Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
228 views
in Technique[技术] by (71.8m points)

python - @patch() doesn't produce expected behavior inside a decorator

I'm testing a view function in Flask that requires authentication. I want to remove the effect of authentication, and so I wrote a test like so:

@patch('auth0_.decode_jwt')
@patch('auth0_.get_token_auth_header')
def test_list_cards_endpoint(self, get_token_header, decode_jwt):
    decode_jwt.return_value = {
        'sub': 'auth0|123',
    }
    headers = {
        'Authorization': 'Bearer 123'
    }
    with current_app.test_request_context('/pay/list-cards', headers=headers):
        list_cards_endpoint()

Which works completely fine. Next I wanted to abstract the above code so I can re-use it all tests and so I turned it into a decorator:

def fake_auth0(f):
    @patch('auth0_.decode_jwt')
    @patch('auth0_.get_token_auth_header')

    def decorated(get_token_header, decode_jwt, *args, **kwargs):
        decode_jwt.return_value = {
            'sub': 'auth0|123',
        }
        headers = {
            'Authorization': 'Bearer 123'
        }
        with current_app.test_request_context('/pay/list-cards', headers=headers):
            return f(*args, **kwargs)

    return decorated

And tried calling it:

@fake_auth0
def test_list_cards_endpoint(self):
    list_cards_endpoint()

And I'm getting an error back, the essence of which is that @patch statements are not working correctly. More specifically, they're not assigning the return_value to be what I want and instead I simply get <MagicMock name='decode_jwt().__getitem__()' id='4540406656'> as a return value for decode_jwt.

So it's like - the mock is working, but the return value is not.

Why might this be?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You're missing the self parameter.

A function is not a method during a class definition when the decorator is applied. The self doesn't get bound until you access it as an attr with a dot, which triggers its descriptor.

And @patch passes its object as the last argument, not the first, so using *args like that isn't going to work.

You could try not passing them as arguments at all:

def fake_auth0(f):
    get_token_header = MagicMock()
    decode_jwt = MagicMock()
    decode_jwt.return_value = {
        'sub': 'auth0|123',
    }

    @patch('auth0_.decode_jwt', decode_jwt)
    @patch('auth0_.get_token_auth_header', get_token_header)
    def decorated(*args, **kwargs):    
        headers = {
            'Authorization': 'Bearer 123'
        }
        with current_app.test_request_context('/pay/list-cards', headers=headers):
            return f(*args, **kwargs)

    return decorated

Or alternatively, pull them out of *args before passing them on:


def fake_auth0(f):
    @patch('auth0_.decode_jwt', return_value={'sub': 'auth0|123'})
    @patch('auth0_.get_token_auth_header')
    def decorated(*args, **kwargs):
        *args, get_token_auth_header, decode_jwt = args
        headers = {
            'Authorization': 'Bearer 123'
        }
        with current_app.test_request_context('/pay/list-cards', headers=headers):
            return f(*args, **kwargs)

    return decorated

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...