# Fake Services e Mocking: Isolando Dependências Externas

**Meta Description:** Aprenda a criar e usar fake services: mocking de APIs, databases, message queues. Como testar sem dependências externas.

---

## Por Que Usar Fakes e Mocks?

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

---

## Tipos de Fakes

### 1. Fake Objects

Simulam comportamento de objetos reais.

```python
# 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.

```python
# 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)

```python
# 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)

```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
// 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)

```javascript
// 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(<PaymentForm />);
  
  // 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

```python
# 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
