JavaScript Avançado

Dominando Testes Unitários Avançados com Vitest: Mocks, Spies e Stubs em Projetos Reais

8 min de leitura

Dominando Testes Unitários Avançados com Vitest: Mocks, Spies e Stubs em Projetos Reais

Introdução: Por que Mocks, Spies e Stubs são Essenciais Testes unitários avançados vão além de simples assertions. Quando você testa uma função que depende de APIs externas, bancos de dados ou módulos complexos, precisa de técnicas sofisticadas para isolar o comportamento do código sob teste. Mocks, spies e stubs são as ferramentas que permitem controlar dependências, simular comportamentos e validar interações entre componentes. O Vitest, moderno e rápido, oferece suporte nativo a essas técnicas através de sua API robusta, tornando-se a escolha ideal para projetos TypeScript e JavaScript contemporâneos. Entendendo os Conceitos Fundamentais Diferenças Práticas Entre Mock, Spy e Stub Um stub substitui uma função real por uma versão simulada que retorna dados pré-configurados, sem registrar chamadas. Um spy envolve uma função existente, permitindo monitorar como ela foi chamada enquanto mantém seu comportamento original. Um mock é uma versão completamente controlada que define comportamentos esperados e valida se foram chamados corretamente. Na prática, considere uma função que busca dados de

<h2>Introdução: Por que Mocks, Spies e Stubs são Essenciais</h2>

<p>Testes unitários avançados vão além de simples assertions. Quando você testa uma função que depende de APIs externas, bancos de dados ou módulos complexos, precisa de técnicas sofisticadas para isolar o comportamento do código sob teste. Mocks, spies e stubs são as ferramentas que permitem controlar dependências, simular comportamentos e validar interações entre componentes. O Vitest, moderno e rápido, oferece suporte nativo a essas técnicas através de sua API robusta, tornando-se a escolha ideal para projetos TypeScript e JavaScript contemporâneos.</p>

<h2>Entendendo os Conceitos Fundamentais</h2>

<h3>Diferenças Práticas Entre Mock, Spy e Stub</h3>

<p>Um <strong>stub</strong> substitui uma função real por uma versão simulada que retorna dados pré-configurados, sem registrar chamadas. Um <strong>spy</strong> envolve uma função existente, permitindo monitorar como ela foi chamada enquanto mantém seu comportamento original. Um <strong>mock</strong> é uma versão completamente controlada que define comportamentos esperados e valida se foram chamados corretamente.</p>

<p>Na prática, considere uma função que busca dados de um servidor:</p>

<pre><code class="language-javascript">// Função sob teste

async function fetchUserData(userId) {

const response = await fetch(/api/users/${userId});

return response.json();

}

// Teste com stub (retorna dados fixos)

import { describe, it, expect, vi } from &#039;vitest&#039;;

describe(&#039;fetchUserData com Stub&#039;, () =&gt; {

it(&#039;deve processar dados do usuário corretamente&#039;, async () =&gt; {

global.fetch = vi.fn().mockResolvedValue({

json: () =&gt; Promise.resolve({ id: 1, name: &#039;João&#039; })

});

const result = await fetchUserData(1);

expect(result.name).toBe(&#039;João&#039;);

});

});</code></pre>

<p>Aqui, <code>fetch</code> foi substituído (stub) por uma versão que retorna sempre o mesmo resultado, isolando a função de teste da rede real.</p>

<h3>Quando Usar Cada Técnica</h3>

<p>Use <strong>stubs</strong> quando precisa apenas de valores de retorno previsíveis e não se importa com chamadas. Use <strong>spies</strong> quando a função original importa, mas você quer monitorar seu comportamento. Use <strong>mocks</strong> quando precisa validar que certas funções foram chamadas com argumentos específicos e em sequência correta. A escolha depende do que você está testando: lógica (stub), integração superficial (spy), ou contrato entre módulos (mock).</p>

<h2>Técnicas Avançadas com Vitest</h2>

<h3>Spies: Monitorando Comportamento Real</h3>

<p>Spies permitem verificar como uma função foi utilizada mantendo sua implementação. Imagine um serviço de notificação:</p>

<pre><code class="language-javascript">class NotificationService {

send(message) {

console.log(Email enviado: ${message});

return true;

}

}

const service = new NotificationService();

const spy = vi.spyOn(service, &#039;send&#039;);

service.send(&#039;Bem-vindo!&#039;);

service.send(&#039;Confirmação&#039;);

expect(spy).toHaveBeenCalledTimes(2);

expect(spy).toHaveBeenNthCalledWith(1, &#039;Bem-vindo!&#039;);

expect(spy).toHaveBeenNthCalledWith(2, &#039;Confirmação&#039;);

spy.mockRestore();</code></pre>

<p>O spy registra todas as chamadas e permite assertions detalhadas sobre argumentos e quantidade de invocações. Note que <code>mockRestore()</code> remove a instrumentação, importante para evitar efeitos colaterais entre testes.</p>

<h3>Mocks: Validando Contratos</h3>

<p>Mocks são ideais para verificar se uma dependência foi usada corretamente. Considere um repositório que precisa comunicar com um banco de dados:</p>

<pre><code class="language-javascript">class UserRepository {

constructor(database) {

this.db = database;

}

createUser(user) {

return this.db.insert(&#039;users&#039;, user);

}

findById(id) {

return this.db.query(&#039;SELECT * FROM users WHERE id = ?&#039;, [id]);

}

}

describe(&#039;UserRepository&#039;, () =&gt; {

it(&#039;deve inserir usuário corretamente&#039;, () =&gt; {

const mockDb = {

insert: vi.fn().mockResolvedValue({ id: 1 })

};

const repo = new UserRepository(mockDb);

repo.createUser({ name: &#039;Maria&#039; });

expect(mockDb.insert).toHaveBeenCalledWith(&#039;users&#039;, { name: &#039;Maria&#039; });

});

it(&#039;deve executar query com parâmetros corretos&#039;, () =&gt; {

const mockDb = {

query: vi.fn().mockResolvedValue([{ id: 1, name: &#039;Maria&#039; }])

};

const repo = new UserRepository(mockDb);

repo.findById(1);

expect(mockDb.query).toHaveBeenCalledWith(

&#039;SELECT * FROM users WHERE id = ?&#039;,

[1]

);

});

});</code></pre>

<p>Este padrão isola completamente a camada de dados, permitindo testar a lógica de negócio sem um banco de dados real.</p>

<h3>Padrão Avançado: Simulando Erros</h3>

<p>Um cenário comum é validar comportamento quando dependências falham:</p>

<pre><code class="language-javascript">class PaymentProcessor {

constructor(gateway) {

this.gateway = gateway;

}

async processPayment(amount) {

try {

const result = await this.gateway.charge(amount);

return { success: true, transactionId: result.id };

} catch (error) {

return { success: false, error: error.message };

}

}

}

describe(&#039;PaymentProcessor&#039;, () =&gt; {

it(&#039;deve tratar falha de gateway graciosamente&#039;, async () =&gt; {

const mockGateway = {

charge: vi.fn().mockRejectedValue(

new Error(&#039;Gateway indisponível&#039;)

)

};

const processor = new PaymentProcessor(mockGateway);

const result = await processor.processPayment(100);

expect(result.success).toBe(false);

expect(result.error).toBe(&#039;Gateway indisponível&#039;);

});

});</code></pre>

<p>Usando <code>mockRejectedValue</code>, simulamos exceções que a dependência pode lançar, validando se a função sob teste as trata corretamente.</p>

<h2>Padrões de Uso e Boas Práticas</h2>

<h3>Limpeza e Isolamento</h3>

<p>Cada teste deve ser independente. Use <code>beforeEach</code> e <code>afterEach</code> para gerenciar estado:</p>

<pre><code class="language-javascript">describe(&#039;API Service&#039;, () =&gt; {

let mockApi;

beforeEach(() =&gt; {

mockApi = {

get: vi.fn(),

post: vi.fn()

};

});

afterEach(() =&gt; {

vi.clearAllMocks();

});

it(&#039;teste 1&#039;, () =&gt; {

mockApi.get.mockResolvedValue({ data: &#039;test&#039; });

expect(mockApi.get).toHaveBeenCalled();

});

it(&#039;teste 2&#039;, () =&gt; {

// mockApi está limpo aqui

expect(mockApi.get).not.toHaveBeenCalled();

});

});</code></pre>

<p><code>vi.clearAllMocks()</code> reseta o histórico de chamadas, evitando contaminação entre testes.</p>

<h3>Evitar Over-Mocking</h3>

<p>Não mocke tudo. Se uma função é simples e sem dependências externas, teste-a de verdade. Mocke apenas dependências que causam efeitos colaterais (I/O, rede, tempo):</p>

<pre><code class="language-javascript">// ✗ Ruim: mocka lógica pura

const add = (a, b) =&gt; a + b;

const mockAdd = vi.fn().mockReturnValue(5);

// ✓ Bom: testa diretamente

expect(add(2, 3)).toBe(5);</code></pre>

<h2>Conclusão</h2>

<p>Dominar mocks, spies e stubs transforma você em um testador profissional. O Vitest oferece APIs claras e poderosas para cada cenário. Lembre-se: <strong>spies monitoram comportamento real</strong>, <strong>stubs substituem com valores fixos</strong>, <strong>mocks validam contratos entre componentes</strong>. Use cada um em seu contexto apropriado, sempre mantendo testes rápidos, isolados e legíveis. A prática leva à excelência — comece a experimentar essas técnicas em seu próximo projeto.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://vitest.dev/api/vi.html" target="_blank" rel="noopener noreferrer">Vitest Documentation - Mocking</a></li>

<li><a href="https://testing-library.com/docs/" target="_blank" rel="noopener noreferrer">Testing Library Best Practices</a></li>

<li><a href="https://github.com/vitest-dev/vitest" target="_blank" rel="noopener noreferrer">JavaScript Testing with Jest and Vitest</a></li>

<li><a href="https://www.pluralsight.com/courses/unit-testing-principles" target="_blank" rel="noopener noreferrer">Unit Testing Principles by Vladimir Khorikov</a></li>

<li><a href="https://martinfowler.com/articles/mocksArentStubs.html" target="_blank" rel="noopener noreferrer">Mock Objects Pattern</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Decorators em TypeScript: Metadata, Reflection e uso com NestJS na Prática
Decorators em TypeScript: Metadata, Reflection e uso com NestJS na Prática

O que são Decorators em TypeScript? Decorators são uma funcionalidade experim...

Boas Práticas de Hooks Customizados em React: Abstraindo Lógica Reutilizável para Times Ágeis
Boas Práticas de Hooks Customizados em React: Abstraindo Lógica Reutilizável para Times Ágeis

Entendendo Hooks Customizados Um Hook customizado é uma função JavaScript que...

Dominando Fastify em Node.js: Alta Performance e Schema Validation com JSON Schema em Projetos Reais
Dominando Fastify em Node.js: Alta Performance e Schema Validation com JSON Schema em Projetos Reais

Por que Fastify? Fastify é um framework web moderno para Node.js que se desta...