#python #unit-testing #mocking
#python #модульное тестирование #издевательство
Вопрос:
Я тестирую функцию просмотра в Flask, которая требует аутентификации. Я хочу убрать эффект аутентификации, и поэтому я написал тест следующим образом:
@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()
Который работает совершенно нормально. Затем я хотел абстрагировать приведенный выше код, чтобы я мог повторно использовать все его тесты, и поэтому я превратил его в декоратор:
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
И попытался вызвать его:
@fake_auth0
def test_list_cards_endpoint(self):
list_cards_endpoint()
И я получаю сообщение об ошибке, суть которого заключается в том, что @patch
операторы работают некорректно. Более конкретно, они не присваивают значение return_value
быть тем, что я хочу, и вместо этого я просто получаю <MagicMock name='decode_jwt().__getitem__()' id='4540406656'>
в качестве возвращаемого значения decode_jwt
.
Так что это похоже на — макет работает, но возвращаемое значение — нет.
Почему это может быть?
Комментарии:
1. @gilch не могли бы вы уточнить немного больше? Каковы последствия того, что вы сказали? Любые ссылки, которыми вы могли бы поделиться, чтобы я мог прочитать?
2. Вы пытались использовать версию диспетчера контекста
patch
(напримерwith patch(...) as get_token_header:
) вместо декораторов? Я не уверен, правильно ли обрабатываются декораторы, если внутри другого декоратора.3. @MrBeanBremen оставлял это в качестве последнего средства — если из этого потока не выйдет ничего хорошего, тогда придется. Хорошее предложение!
Ответ №1:
Вам не хватает self
параметра.
Функция не является методом во время определения класса, когда применяется декоратор. self
Не привязывается, пока вы не получите к нему доступ как к attr с точкой, которая запускает его дескриптор.
И @patch
передает свой объект в качестве последнего аргумента, а не первого, поэтому *args
подобное использование не сработает.
Вы могли бы попробовать вообще не передавать их в качестве аргументов:
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
Или, в качестве альтернативы, извлеките их из *args
, прежде чем передавать их дальше:
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
Ответ №2:
Приведенное выше предложение @gilch работает над устранением исходной ошибки, но затем я начинаю сталкиваться с новыми, а именно с этим:
AttributeError: Attributes cannot start with 'assert' or 'assret'
Я полагаю, это связано с тем, что декоратор определен вне класса, но его определение внутри класса также сопряжено с проблемами.
В общем, я отказался от декораторов и просто написал менеджер контекста:
@contextlib.contextmanager
def auth0_faker(url):
with patch('auth0_.decode_jwt') as mock_decode_jwt:
with patch('auth0_.get_token_auth_header') as mock_auth_header:
mock_decode_jwt.return_value = {
'sub': 'auth0|123',
}
headers = {
'Authorization': 'Bearer 123'
}
with current_app.test_request_context(url, headers=headers):
yield
Я нахожу это намного проще для чтения и понимания.