rennerocha.com/content/posts/20221107-testando-multiplas-chamadas-de-um-mock.md

4 KiB

title publishdate tags slug
Testando múltiplas chamadas de uma função com mocks 2022-11-07
python
testes
pytest
testando-multiplas-chamadas-de-um-mock

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 uma fixture do pytest-mock auxiliar de unittest.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.