Fake Services e Mocking: Isolando Dependências Externas

Testes Automatizados · 6 de julho de 2026

📖 6 min de leitura

Por Que Usar Fakes e Mocks?

  • Isolamento: Testes não dependem de serviços externos
    1. Velocidade: Mocks são mais rápidos que serviços reais
    2. Confiabilidade: Não há flaky tests por dependências
    3. Cobertura: Testar edge cases difíceis de reproduzir

Tipos de Fakes

1. Fake Objects

Simulam comportamento de objetos reais.

# fakes/user_repository.py
class FakeUserRepository:
    def __init__(self):
        self.users = {}
        self._id_counter = 1

def save(self, user):
if not user.id:
user.id = self._id_counter
self._id_counter += 1
self.users[user.id] = user
return user

def find_by_id(self, id):
return self.users.get(id)

def find_by_email(self, email):
for user in self.users.values():
if user.email == email:
return user
return None

def delete(self, id):
if id in self.users:
del self.users[id]
return True
return False

# Uso em testes
def test_create_user():
repo = FakeUserRepository()
service = UserService(repo)

user = service.create_user("João", "joao@example.com")

assert user.id is not None
assert user.name == "João"
assert repo.find_by_email("joao@example.com") == user

2. Fake APIs

Simulam endpoints HTTP.

# fakes/http_fake.py
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
from typing import Dict, Callable

class FakeAPIHandler(BaseHTTPRequestHandler):
routes: Dict[str, Callable] = {}

def do_GET(self):
self._handle_request('GET')

def do_POST(self):
self._handle_request('POST')

def do_PUT(self):
self._handle_request('PUT')

def do_DELETE(self):
self._handle_request('DELETE')

def _handle_request(self, method):
handler = self.routes.get((method, self.path))
if handler:
response = handler(self)
self._send_response(response)
else:
self._send_response({'error': 'Not found'}, 404)

def _send_response(self, data, status=200):
self.send_response(status)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())

def log_message(self, format, *args):
pass # Silenciar logs

class FakeAPIServer:
def __init__(self, port=8080):
self.port = port
self.server = None
self.thread = None
self.routes = {}

def register(self, method, path, handler):
self.routes[(method, path)] = handler
FakeAPIHandler.routes = self.routes

def start(self):
self.server = HTTPServer(('localhost', self.port), FakeAPIHandler)
self.thread = Thread(target=self.server.serve_forever)
self.thread.daemon = True
self.thread.start()

def stop(self):
if self.server:
self.server.shutdown()

# Uso
def payment_handler(request):
return {
'transaction_id': 'tx_123',
'status': 'approved',
'amount': 99.90
}

fake_api = FakeAPIServer(8080)
fake_api.register('POST', '/api/payment', payment_handler)
fake_api.start()

# Seus testes aqui

fake_api.stop()

3. Fake Databases (SQLite)

# fakes/database.py
import sqlite3
import os

class FakeDatabase:
def __init__(self, in_memory=True):
if in_memory:
self.conn = sqlite3.connect(':memory:')
else:
self.db_path = 'test.db'
self.conn = sqlite3.connect(self.db_path)

self.conn.row_factory = sqlite3.Row
self._create_tables()

def _create_tables(self):
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
''')
cursor.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL
)
''')
self.conn.commit()

def execute(self, query, params=None):
cursor = self.conn.cursor()
if params:
cursor.execute(query, params)
else:
cursor.execute(query)
self.conn.commit()
return cursor

def fetchone(self, query, params=None):
cursor = self.execute(query, params)
return cursor.fetchone()

def fetchall(self, query, params=None):
cursor = self.execute(query, params)
return cursor.fetchall()

def close(self):
self.conn.close()
if hasattr(self, 'db_path') and os.path.exists(self.db_path):
os.remove(self.db_path)

# Uso
def test_user_crud():
db = FakeDatabase()

# Create
db.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
('João', 'joao@example.com')
)

# Read
user = db.fetchone('SELECT * FROM users WHERE email = ?', ('joao@example.com',))
assert user['name'] == 'João'

db.close()


Mocking Libraries

unittest.mock (Python)

from unittest.mock import Mock, patch, MagicMock

# Mock básico
def test_payment():
mock_gateway = Mock()
mock_gateway.charge.return_value = {'status': 'success'}

result = process_payment(100, mock_gateway)

mock_gateway.charge.assert_called_once_with(100)
assert result == 'success'

# Patch
@patch('myapp.services.payment_gateway')
def test_with_patch(mock_gateway):
mock_gateway.charge.return_value = {'status': 'success'}

result = process_payment(100)

mock_gateway.charge.assert_called_once()

# Spy (partial mock)
def test_spy():
real_service = PaymentService()
spy_service = Mock(wraps=real_service)

spy_service.process(100)

assert spy_service.process.called
assert real_service.internal_method.called # Real method foi chamado

WireMock (Java/Server)

// Java WireMock
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;

WireMockServer wireMock = new WireMockServer(8080);
wireMock.start();

// Stub
wireMock.stubFor(post(urlEqualTo("/api/payment"))
.withRequestBody(containing("amount"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{"transaction_id":"tx_123","status":"approved"}")));

// Test
RestAssured.given()
.contentType("application/json")
.body("{"amount":100}")
.when()
.post("http://localhost:8080/api/payment")
.then()
.statusCode(200)
.body("status", equalTo("approved"));

wireMock.stop();

MSW (Mock Service Worker)

// Mock API com MSW
import { setupWorker, rest } from 'msw';

const handlers = [
rest.post('/api/payment', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
transaction_id: 'tx_123',
status: 'approved',
amount: req.body.amount
})
);
}),

rest.get('/api/users/:id', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
id: req.params.id,
name: 'João Silva',
email: 'joao@example.com'
})
);
}),

rest.post('/api/users', (req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
id: 'new-user-id',
...req.body
})
);
})
];

export const worker = setupWorker(...handlers);

// No test
import { worker } from './mocks/browser';

beforeAll(() => worker.start());
afterAll(() => worker.stop());

test('payment flow', async () => {
render();

// MSW intercepta requisição
await userEvent.type(screen.getByLabelText('Valor'), '100');
await userEvent.click(screen.getByRole('button', { name: /pagar/i }));

expect(await screen.findByText(/transação aprovada/i)).toBeInTheDocument();
});


TestContainers vs Fakes

Aspecto TestContainers Fakes
Fidelidade Real service Simulado
Velocidade Lento (minutos) Rápido (ms)
Complexidade Alta Baixa
Bugs encontrados Mais realistas Menos
Manutenção Baixa Alta (manter fakes)

Quando Usar Cada

# Use FAKE quando:
# - Precisa de respostas específicas/edge cases
# - Performance é crítica
# - Serviço externo é lento/não disponível
# - Quer testar comportamento, não implementação

# Use TestContainers quando:
# - Precisa de comportamento real
# - Testando integrações complexas
# - Quer detectar problemas realistas
# - Manutenção do fake seria muito complexa


Conclusão

Mocks e fakes são essenciais para testes isolados. As chaves são:

  1. Isolar – Testes não devem depender de externos
  2. Velocidade – Fakes são rápidos
  3. Verossimilhança – Fakes devem ser realistas
  4. Manutenção – Mocks automáticos vs manuais
  5. Combinar – Fakes + TestContainers quando necessário