Em algumas situações durante o teste e alguma função, queremos confirmar que uma determinada função foi chamada uma quantidade de vezes esperada e também com os argumentos corretos.
Isso pode ser obtido utilizando o mock da biblioteca padrão do Python ou, caso estejamos utilizando o pytest, com o auxílio da biblioteca pytest-mock.
Como exemplo, vamos testar a função main()
a seguir. Ela obtém uma lista de URLs que neste exemplo é fixa, mas em um caso real, ela pode ser o resultado de uma condição especial do sistema que você está testando.
# app.py
import requests
def do_something_with_response(response):
...
def get_urls_to_call():
return [
"http://www.url1.com",
"http://www.url2.com",
]
def main():
urls_to_call = get_urls_to_call()
for url in urls_to_call:
response = requests.post(url)
do_something_with_response(response)
Meu objetivo aqui é verificar se requests.post()
foi chamado apenas duas vezes e se nessas duas vezes, passamos como argumento o valor das duas URLs retornadas pela função get_urls_to_call()
.
O primeiro passo é criar um patch
de requests.post
. Fazemos isso da seguinte maneira:
# test_app.py
def test_assert_called_with_all_urls(mocker):
requests_post_mock = mocker.patch("app.requests.post")
A partir desse momento, qualquer chamada de requests.post
dentro do módulo app
será uma chamada a um MagicMock
e não a função original. Atente-se que o patch
não é realizado com requests.post
e sim com app.requests.post
.
Para mais detalhes sobre como usar corretamente a função
patch
, assista esta apresentação de Lisa Roach na PyCon 2018.
Em seguida podemos fazer a chamada na função que estamos testando:
# test_app.py
from app import main
def test_assert_called_with_all_urls(mocker):
requests_post_mock = mocker.patch("app.requests.post")
main()
mocker
é apenas umafixture
dopytest-mock
auxiliar deunittest.mock
.
E agora iremos verificar as chamadas que foram realizadas e se or argumentos passados foram os corretos:
from app import main
def test_assert_called_with_all_urls(mocker):
requests_post_mock = mocker.patch("app.requests.post")
main()
# Garante que 'requests.post' foi chamado duas vezes
# e cada uma delas a URL correta foi passada como argumento
requests_post_mock.assert_has_calls(
[
mocker.call("http://www.url1.com"),
mocker.call("http://www.url2.com"),
]
)
Existem outras validações possível, como por exemplo verificarmos se requests.post
foi chamado uma única vez (assert_called_once
) ou mesmo se a função não foi chamada nenhuma vez (assert_not_called
) o que pode ser útil para testar condições de erro.
Só é preciso tomar cuidado para não abusar desse tipo de teste, já que ele é altamente acoplado a implementação do seu código e dependendo de como ele é definido e colocado no seu projeto, eventualmente ele pode não testar adequadamente o que você quer testar.