---
title: "Mock em Python: patch, side_effect e AsyncMock"
url: "https://python.dev.br/blog/python-unittest-mock-patch-testes/"
markdown_url: "https://python.dev.br/blog/python-unittest-mock-patch-testes.MD"
description: "Teste código Python sem chamar APIs reais: unittest.mock, patch no lugar certo, autospec, side_effect e AsyncMock, com exemplos executáveis em pytest."
date: "2026-10-12"
author: "Equipe Python Dev BR"
---

# Mock em Python: patch, side_effect e AsyncMock

Teste código Python sem chamar APIs reais: unittest.mock, patch no lugar certo, autospec, side_effect e AsyncMock, com exemplos executáveis em pytest.


Para **testar Python sem chamar APIs reais**, use `unittest.mock`: crie um substituto com `create_autospec`, configure `return_value` ou `side_effect` e confira as chamadas. Quando a dependência já foi importada pelo módulo, aplique `patch` **onde ela é usada**, não necessariamente onde foi definida. Para funções assíncronas, use `AsyncMock` e verificações de `await`.

Isso permite testar uma consulta de CEP, um envio de e-mail ou uma integração de CRM sem rede, cobrança ou mensagens reais. O objetivo não é simular todo o programa: é isolar a fronteira externa e continuar executando a sua regra de negócio.

Neste guia, os exemplos usam Python 3.10 ou superior e pytest. O mock vem da biblioteca padrão; **não é necessário instalar pytest-mock**. Se testes ainda são novidade, comece pelo [guia de pytest](/guias/testes-com-pytest/).

## Mock, MagicMock, patch ou monkeypatch?

| Recurso | Quando usar | Cuidado principal |
|---|---|---|
| `Mock` | Substituir uma função ou objeto e registrar chamadas | Sem especificação, aceita atributos inexistentes |
| `MagicMock` | Simular objetos com métodos especiais, como gerenciadores de contexto | Não deixar comportamento implícito esconder erros |
| `create_autospec` | Criar substituto com interface e assinatura conhecidas | Não valida os tipos das anotações |
| `patch` | Substituir temporariamente uma referência do módulo | Escolher o namespace correto |
| `AsyncMock` | Simular uma função aguardada com `await` | Ser chamado não significa ser aguardado |
| `monkeypatch` do pytest | Trocar variáveis de ambiente, atributos ou diretório | Não oferece sozinho as verificações de chamadas de um mock |

`patch` é o mecanismo de substituição; `Mock` é um tipo de objeto substituto. Eles se complementam. Você também pode **injetar uma dependência** como argumento e passar um mock, sem aplicar patch em nenhum módulo.

## Projeto executável: consultar um CEP sem rede nos testes

Crie uma pasta com três arquivos:

```text
exemplo-mock/
├── clientes.py
├── pedidos.py
└── test_pedidos.py
```

Dentro de um [ambiente virtual](/guias/criando-virtual-environment/), instale o executor:

```bash
python -m pip install pytest
```

No terminal Linux/macOS, o comando pode ser `python3`; no Windows, pode ser `py`. Use sempre o interpretador do ambiente do projeto. Um [ModuleNotFoundError](/blog/modulenotfounderror-python-pacote-instalado/) geralmente indica que a instalação e a execução usaram ambientes diferentes.

### 1. A fronteira externa: clientes.py

Este cliente usa apenas a biblioteca padrão e faz uma consulta ao ViaCEP. Os testes vão substituir a função inteira, portanto não executam esta chamada HTTP:

```python
# clientes.py
import json
from urllib.error import URLError
from urllib.request import urlopen


def buscar_cep(cep: str) -> dict:
    if len(cep) != 8 or not cep.isascii() or not cep.isdigit():
        raise ValueError("CEP deve conter oito dígitos ASCII")

    try:
        with urlopen(
            f"https://viacep.com.br/ws/{cep}/json/", timeout=5
        ) as resposta:
            dados = json.load(resposta)
    except (URLError, TimeoutError) as exc:
        raise ConnectionError("Serviço de CEP indisponível") from exc

    if dados.get("erro"):
        raise ValueError("CEP não encontrado")
    return dados
```

O timeout evita uma espera indefinida. Este é um cliente didático: uma aplicação real também precisa decidir como tratar JSON inválido e respostas fora do contrato. Para normalização, validação e diferenças entre provedores, veja [CPF, CNPJ e CEP com BrasilAPI e ViaCEP](/blog/validar-cpf-cnpj-cep-python-brasilapi-viacep/).

### 2. A regra de negócio: pedidos.py

O módulo transforma a resposta externa em um resumo e define uma resposta para indisponibilidade:

```python
# pedidos.py
from clientes import buscar_cep


def resumir_destino(cep: str) -> str:
    try:
        dados = buscar_cep(cep)
    except ConnectionError:
        return "Consulta temporariamente indisponível"

    return f"{dados['localidade']}/{dados['uf']}"
```

O código deixa `ValueError` propagar: entrada inválida não é a mesma coisa que falha de rede. O teste precisa preservar essa distinção, em vez de assumir que todo erro deve ser escondido.

### 3. Teste de sucesso com patch e autospec

Salve em `test_pedidos.py`:

```python
# test_pedidos.py
from unittest.mock import patch

import pytest

from pedidos import resumir_destino


def test_resumo_do_destino():
    with patch("pedidos.buscar_cep", autospec=True) as consulta:
        consulta.return_value = {
            "localidade": "São Paulo",
            "uf": "SP",
        }

        resultado = resumir_destino("01001000")

        assert resultado == "São Paulo/SP"
        consulta.assert_called_once_with("01001000")
```

Rode, na pasta dos arquivos:

```bash
python -m pytest -q
```

O resultado esperado neste ponto é um teste aprovado. Não é necessário ter internet para executá-lo. O `with` restaura a referência original ao terminar, inclusive se uma asserção falhar.

### Por que patch em pedidos.buscar_cep, e não em clientes.buscar_cep?

`from clientes import buscar_cep` cria uma referência no namespace de `pedidos`. Trocar depois `clientes.buscar_cep` não troca automaticamente a referência já importada em `pedidos`.

A regra é **substituir o nome que o código consulta**:

| Importação no módulo sob teste | Chamada realizada | Alvo típico de patch |
|---|---|---|
| `from clientes import buscar_cep` em `pedidos.py` | `buscar_cep(cep)` | `pedidos.buscar_cep` |
| `import clientes` em `pedidos.py` | `clientes.buscar_cep(cep)` | `pedidos.clientes.buscar_cep` |

No segundo caso, o atributo pertence ao mesmo objeto de módulo `clientes`; substituir `clientes.buscar_cep` também alcança essa chamada. O primeiro caso é a armadilha mais comum. Se o teste ainda acessa a rede, confira o import e o alvo antes de suspeitar do pytest.

## side_effect: falhas e resultados sucessivos

Acrescente estes testes ao mesmo `test_pedidos.py`. O `pytest` e o `patch` já foram importados no início do arquivo.

```python
# Acrescente a test_pedidos.py

def test_servico_indisponivel():
    with patch("pedidos.buscar_cep", autospec=True) as consulta:
        consulta.side_effect = ConnectionError("timeout simulado")

        assert resumir_destino("01001000") == (
            "Consulta temporariamente indisponível"
        )
        consulta.assert_called_once_with("01001000")


def test_cep_invalido_nao_e_ocultado():
    with patch("pedidos.buscar_cep", autospec=True) as consulta:
        consulta.side_effect = ValueError("CEP inválido")

        with pytest.raises(ValueError, match="CEP inválido"):
            resumir_destino("abc")


def test_duas_consultas_com_resultados_diferentes():
    with patch("pedidos.buscar_cep", autospec=True) as consulta:
        consulta.side_effect = [
            ConnectionError("primeira consulta falhou"),
            {"localidade": "Recife", "uf": "PE"},
        ]

        assert resumir_destino("50030000") == (
            "Consulta temporariamente indisponível"
        )
        assert resumir_destino("50030000") == "Recife/PE"
        assert consulta.call_count == 2
```

A lista produz um item por chamada. Quando o item é uma exceção, ela é levantada; quando é um valor, ele é retornado. Ao esgotar a lista, ocorre `StopIteration`.

**Esse último teste não comprova retry automático:** quem chama duas vezes é o próprio teste. Para testar uma política de retry, execute a função que contém essa política uma vez e verifique suas tentativas, limites e condições. Não simule atrasos reais com `sleep` na suíte rápida; injete ou substitua a função de espera.

## Injeção de dependência com create_autospec

Se você controla o desenho da aplicação, receber o cliente como argumento reduz a dependência de caminhos de import. Este exemplo independente mostra o padrão:

```python
from unittest.mock import create_autospec


def enviar_email(destinatario: str, assunto: str) -> None:
    raise NotImplementedError("Adaptador externo fora deste exemplo")


def avisar_candidato(email: str, enviar) -> None:
    enviar(email, "Nova vaga Python")


substituto = create_autospec(enviar_email, spec_set=True)
avisar_candidato("candidato@example.com", substituto)
substituto.assert_called_once_with(
    "candidato@example.com", "Nova vaga Python"
)
```

O endereço é fictício e nenhum e-mail é enviado. O adaptador real pode ser passado na inicialização da aplicação; nos testes, passe o substituto.

`autospec` ajuda a detectar chamadas incompatíveis com a assinatura. `spec_set` restringe a criação de atributos que não existem na especificação. Esses recursos não conferem tipos como `str`, não validam a forma do dicionário retornado e não garantem compatibilidade com a API remota. Configure os dados simulados de acordo com o contrato real.

Um `Mock` sem especificação pode aceitar um método digitado errado e deixar o teste passar. Por isso, prefira uma interface pequena e conhecida a um objeto genérico com dezenas de atributos inventados.

## AsyncMock: verificar chamadas aguardadas

Para código [async/await](/blog/python-async-await/), uma chamada cria um objeto aguardável. É possível chamar a função sem executar `await`, o que seria um bug. Por isso, a verificação adequada é `assert_awaited_once_with`:

```python
import asyncio
from unittest.mock import AsyncMock


async def consultar_status(cliente, codigo: str) -> str:
    dados = await cliente(codigo)
    return dados["status"]


async def demonstrar():
    cliente = AsyncMock(return_value={"status": "processando"})

    resultado = await consultar_status(cliente, "pedido-123")

    assert resultado == "processando"
    cliente.assert_awaited_once_with("pedido-123")


asyncio.run(demonstrar())
```

Esse exemplo roda como script, sem plugin assíncrono do pytest. Em uma aplicação que já tem um event loop ativo, não chame `asyncio.run` dentro dele: use `await demonstrar()` no fluxo assíncrono existente. Ao criar mocks para clientes reais, use a especificação da interface em vez de copiar o objeto genérico de demonstração sem adaptação.

## O que não deve ser mockado?

Prefira manter reais as transformações locais, cálculos, validações e estruturas de dados baratas. Se toda função interna é substituída, o teste apenas confirma o roteiro que você escreveu para os mocks.

Para arquivos temporários, a fixture `tmp_path` do pytest geralmente é mais útil que simular cada chamada de `open`. Para configurações, use `monkeypatch.setenv` e `monkeypatch.delenv`, como no guia de [variáveis de ambiente](/blog/python-dotenv-env-vars-config/). Para vários cenários da mesma regra, combine os testes com [pytest.mark.parametrize](/blog/pytest-parametrize-testes-parametrizados-python/).

Separe três responsabilidades:

1. **Teste unitário da regra:** substitui rede, envio e outros efeitos externos; verifica resultados e interações relevantes.
2. **Teste do adaptador:** verifica parsing, status HTTP, timeouts e tradução de erros, usando transporte ou servidor controlado.
3. **Teste de integração:** confirma o contrato com sandbox ou serviço permitido, separado da suíte rápida e sem dados pessoais reais.

Os testes de `resumir_destino` não cobrem o corpo de `buscar_cep`. Isso é intencional, mas precisa estar explícito: um mock bem configurado não prova que o endpoint continua disponível nem que o cliente trata todos os formatos de resposta.

## Checklist para mocks confiáveis

- O alvo de `patch` é o nome consultado pelo módulo?
- O teste executa a regra real, e não uma versão mockada dela?
- A resposta simulada representa um contrato plausível?
- Há cenários de sucesso, indisponibilidade e entrada inválida?
- As verificações de chamadas são relevantes ao comportamento, e não detalhes internos frágeis?
- `autospec` protege a assinatura quando aplicável?
- Toda substituição é restaurada por contexto, fixture ou decorator?
- Testes assíncronos conferem o `await`?
- Existe cobertura separada para o adaptador e suas integrações?

## Perguntas frequentes

### Preciso instalar unittest.mock?

Não. Ele acompanha Python. Instale pytest para executar os testes deste guia. O plugin pytest-mock oferece a fixture `mocker`, mas é uma opção de conveniência, não uma exigência.

### Por que meu patch não impede a chamada real?

Normalmente o patch foi aplicado no módulo de origem, enquanto o código usa uma referência importada em outro namespace. Inspecione a linha de import e substitua o nome usado no módulo sob teste.

### return_value ou side_effect?

Use `return_value` para um retorno constante. Use `side_effect` para exceções, retornos calculados ou sequências de respostas. Uma sequência exige que você controle quantas chamadas são esperadas.

### Mock ou monkeypatch?

`monkeypatch` é conveniente para mudanças temporárias no ambiente e em atributos durante testes pytest. `unittest.mock` adiciona objetos com histórico de chamadas e comportamento configurável. É possível usar ambos no mesmo projeto.

### Posso confiar só nos mocks para validar uma integração?

Não. Eles testam seu comportamento diante de uma simulação. Verifique o adaptador e mantenha testes de integração apropriados, sem transformar a suíte unitária em um consumidor de serviços reais.

## Próximo passo

Execute os quatro testes do projeto e altere temporariamente o argumento da consulta em `pedidos.py`: a verificação `assert_called_once_with` deve detectar a mudança. Depois, escreva testes próprios para o adaptador, cobrindo uma resposta inválida e um CEP não encontrado.

Para um projeto de portfólio, documente quais testes rodam offline e quais dependem de sandbox. Essa distinção comunica maturidade de engenharia melhor que uma porcentagem de cobertura isolada; veja também a trilha de [QA e automação de testes com Python](/blog/python-qa-automacao-testes-carreira/).

## Referências

- [unittest.mock — documentação oficial](https://docs.python.org/3/library/unittest.mock.html)
- [Where to patch — namespace correto](https://docs.python.org/3/library/unittest.mock.html#where-to-patch)
- [Exemplos oficiais de unittest.mock](https://docs.python.org/3/library/unittest.mock-examples.html)
- [monkeypatch — documentação do pytest](https://docs.pytest.org/en/stable/how-to/monkeypatch.html)
