11
respostas

Teste Unitário VS Teste de integração.

it('A senha do usuário precisa ser criptografada quando for salva no banco de dados', async () => {
    const data = {
      nome: 'John Doe',
      email: 'johndoe@example.com',
      senha: 'senha123',
    };

    const resultado = await authService.cadastrarUsuario(data);
    const senhaIguais = await bcryptjs.compare('senha123', resultado.content.senha);

    expect(senhaIguais).toStrictEqual(true);

    await Usuario.excluir(resultado.content.id);
  });
async cadastrarUsuario(data) {
    try {
      if(!data.senha) {
        throw new Error('A senha de usuário é obrigatório!');
      }
  
      data.senha = await bcryptjs.hash(data.senha, 8);
      
      const usuario = new Usuario(data);
      const resposta = await usuario.salvar(usuario);
      return { message: 'usuario criado', content: resposta };
    } catch (err) {
      throw new Error(err.message);
    }
  }

1)O teste feito é unitário ou de integração e pq?
2)Se é de integração tbm precisariamos fazer um teste unitário? Se sim pq?
3)Se precisaria fazer um teste unitário, é preciso saber da implementação do método "cadastrar", saber que tem o método "salvar" dentro dele? Se não souber disso não tem como fazer o teste unitário pois não tem como adivinhar q o método "salvar" está dentro?

11 respostas

Olá Luidi! Joia?

Vamos analisar sua dúvida sobre os testes:

  1. O teste feito é unitário ou de integração e por quê?

    O teste que você mostrou é um teste de integração. Isso porque ele verifica a interação entre diferentes partes do seu sistema: o método cadastrarUsuario e o banco de dados (através do método salvar). Testes de integração são usados para garantir que diferentes módulos ou serviços funcionem bem juntos.

  2. Se é de integração também precisaríamos fazer um teste unitário? Se sim, por quê?

    Sim, é importante também fazer testes unitários. Testes unitários focam em testar partes isoladas do código, como funções ou métodos individuais, sem depender de outras partes do sistema. Isso ajuda a identificar problemas específicos em uma função ou método sem a interferência de outros componentes. No seu caso, você poderia criar um teste unitário para o método cadastrarUsuario, verificando, por exemplo, se a senha é realmente criptografada antes de ser salva.

  3. Se precisaria fazer um teste unitário, é preciso saber da implementação do método "cadastrar", saber que tem o método "salvar" dentro dele?

    Para criar um teste unitário eficaz, você não precisa necessariamente conhecer a implementação interna do método, mas sim entender o comportamento esperado dele. No entanto, conhecer que o método salvar é chamado pode ajudar a criar mocks ou stubs para isolar o teste do banco de dados. Isso é importante para garantir que o teste unitário não dependa de interações reais com o banco de dados, mantendo o foco apenas na lógica do método cadastrarUsuario.

Espero ter ajudado e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.
const resultado = await authService.cadastrarUsuario(data);
    const senhaIguais = await bcryptjs.compare('senha123', resultado.content.senha);

Aqui comparamos a senha com o resultado depois do hash para verificar se são iguais. Mas não seria melhor buscar no banco o registro e daí sim comparar pra ter certeza de que está no banco certinho? Ou assim já basta?

Oi, Luidi! Como vai?

Nesse cenário, comparar com resultado.content.senha já é suficiente para validar a regra “a senha foi criptografada”, porque você está verificando que o hash retornado bate com a senha em texto plano via bcryptjs.compare. Isso confirma que o valor armazenado no objeto retornado é um hash válido daquela senha.

Se você buscar no banco depois, você não melhora a verificação de hash em si — você só passa a testar outra coisa também: a integração com o persistência/DAO/banco (ou seja, que o salvar() realmente persistiu e que o buscar()/find realmente recupera). Isso vira um teste mais “pesado” e com mais pontos de falha (conexão, migrations, limpeza, etc.).

Faça assim, diretamente:

  • Para testar criptografia (regra de negócio): mantenha o teste como está, comparando com o que o método retorna, e adicione uma asserção extra para garantir que não salvou em texto plano.
  • Para testar persistência (integração): crie outro teste que salva e depois busca no banco e valida que o senha recuperado continua sendo um hash (e não igual ao texto plano).

Veja este exemplo:


// Teste focado na REGRA: senha criptografada (não precisa buscar no banco)
it('deve criptografar a senha antes de retornar o usuário criado', async () => {
  const data = { nome: 'John', email: 'john@example.com', senha: 'senha123' };

  const resultado = await authService.cadastrarUsuario(data);

  // garante que NÃO é texto plano
  expect(resultado.content.senha).not.toBe('senha123');

  // garante que o hash corresponde à senha original
  const ok = await bcryptjs.compare('senha123', resultado.content.senha);
  expect(ok).toBe(true);

  await Usuario.excluir(resultado.content.id);
});

// Teste de INTEGRAÇÃO: confirma que persistiu e recuperou do banco corretamente
it('deve persistir o hash da senha no banco', async () => {
  const data = { nome: 'John', email: 'john2@example.com', senha: 'senha123' };

  const resultado = await authService.cadastrarUsuario(data);

  // Busca no banco (ajuste para o método real do seu projeto)
  const usuarioNoBanco = await Usuario.buscarPorId(resultado.content.id);

  expect(usuarioNoBanco.senha).not.toBe('senha123');

  const ok = await bcryptjs.compare('senha123', usuarioNoBanco.senha);
  expect(ok).toBe(true);

  await Usuario.excluir(resultado.content.id);
});

Fico à disposição!

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Nesse que é a primeira resposta, tu diz "Para criar um teste unitário eficaz, você não precisa necessariamente conhecer a implementação interna do método, mas sim entender o comportamento esperado dele.", mas todos testes unitários são caixa branca, ou seja, conhecemos a implementação né? Fiquei confuso.

Oi, Luidi!

Nem todo teste unitário é caixa branca. Teste unitário e caixa branca são conceitos diferentes. Talvez você tenha definido isso todo por conta da minha fala nessa postagem, mas eu estava simplificando para o contexto geral.

  • Teste unitário → testa uma unidade isolada (função ou método).
  • Caixa branca → testa conhecendo a implementação interna.
  • Caixa preta → testa apenas comportamento (entrada e saída).

Um teste unitário pode ser caixa preta. Na verdade, essa é a abordagem mais recomendada: testar o comportamento esperado, não os detalhes internos.

No seu caso, o foco do teste unitário de cadastrarUsuario deve ser:

  • Se recebe senha → retorna usuário com senha criptografada
  • Se não recebe senha → lança erro

Você não precisa saber que existe usuario.salvar() internamente. O que você precisa saber é:

  • Qual é a entrada
  • Qual é o resultado esperado

Se quiser isolar totalmente como teste unitário, você pode mockar a dependência do banco (Usuario), assim o teste não depende da implementação real.

Veja este exemplo:


jest.spyOn(Usuario.prototype, 'salvar').mockResolvedValue({
  id: 1,
  nome: 'John Doe',
  email: 'johndoe@example.com',
  senha: await bcryptjs.hash('senha123', 8)
});

it('deve criptografar a senha antes de salvar', async () => {
  const data = {
    nome: 'John Doe',
    email: 'johndoe@example.com',
    senha: 'senha123'
  };

  const resultado = await authService.cadastrarUsuario(data);

  expect(resultado.content.senha).not.toBe('senha123');

  const senhaValida = await bcryptjs.compare(
    'senha123',
    resultado.content.senha
  );

  expect(senhaValida).toBe(true);
});

Perceba que:

  • Estamos testando apenas o comportamento
  • Não dependemos de banco real
  • Não precisamos testar se salvar() funciona

Então o ponto importante é:

Teste unitário não significa testar implementação interna.
Significa testar uma unidade isolada.

Se você escreve teste baseado na estrutura interna, ele fica frágil. Se amanhã você mudar a implementação (ex: trocar salvar por outro método), o teste quebra mesmo que o comportamento continue correto.

O ideal é focar em:

  • Contrato do método
  • Entrada
  • Saída
  • Efeitos esperados

Isso reduz o acoplamento e melhora a qualidade dos testes.

Fico à disposição.

1)Mas no caso desse teste, o usuário vai ser cadastrado no banco, pois o método "salvar" está lá dentro. Isso não vira teste de integração sem querer? E se o banco cair o teste falha?
2)Se nós mockarmos o "salvar", nós precisamos saber que ele está lá dentro, se não, não tem como saber que ele está lá dentro do "cadastrarUsuario", então pra mockar precisamos conhecer a implementação, então quando tem "mock" é caixa branca?

Aqui está o método "cadastrarUsuario":

async cadastrarUsuario(data) {
    try {
      if (!data.nome) {
        throw new Error('O nome de usuário é obrigatório!');
      }
  
      if (!data.email) {
        throw new Error('O email de usuário é obrigatório!');
      }

      if (!data.senha) {
        throw new Error('A senha de usuário é obrigatório!');
      }

      const usuarioCadastrado = await Usuario.pegarPeloEmail(data.email);

      if (usuarioCadastrado) {
        throw new Error('O email já esta cadastrado!');
      }

      data.senha = await bcryptjs.hash(data.senha, 8);
      
      const usuario = new Usuario(data);
      const resposta = await usuario.salvar(usuario);
      return { message: 'usuario criado', content: resposta };
    } catch (err) {
      throw new Error(err.message);
    }
  }

Oi, Luidi!

Sim: do jeito que seu teste está hoje, ele vira integração sem querer se usuario.salvar() realmente grava no banco (porque você está atravessando service → model → persistência). Nesse caso, se o banco cair o teste falha, e isso é um sinal típico de teste de integração (dependência externa).

Para ter teste unitário de verdade nesse método, faça mock da persistência (e, se quiser, do pegarPeloEmail) e valide somente o comportamento: hash gerado e uso do hash no salvar, sem bater no banco.

Veja este exemplo (unitário, sem banco), validando que:

  • a senha enviada para salvar não é texto plano
  • a senha enviada para salvar é um hash compatível com a senha original
  • pegarPeloEmail foi chamado para validar duplicidade
import bcryptjs from 'bcryptjs';
import Usuario from '../models/Usuario';
import authService from '../services/authService';

describe('authService.cadastrarUsuario (unit)', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('deve criptografar a senha e enviar o hash para salvar (sem banco)', async () => {
    const data = {
      nome: 'John Doe',
      email: 'johndoe@example.com',
      senha: 'senha123',
    };

    // evita bater no banco ao checar email
    jest.spyOn(Usuario, 'pegarPeloEmail').mockResolvedValue(null);

    // intercepta o salvar sem persistir
    const salvarSpy = jest
      .spyOn(Usuario.prototype, 'salvar')
      .mockImplementation(async function (usuario) {
        // simula retorno do "salvar"
        return {
          id: 1,
          nome: usuario.nome,
          email: usuario.email,
          senha: usuario.senha,
        };
      });

    const resultado = await authService.cadastrarUsuario({ ...data });

    // garante que fez a validação de email (sem banco real)
    expect(Usuario.pegarPeloEmail).toHaveBeenCalledWith(data.email);

    // garante que chamou salvar uma vez
    expect(salvarSpy).toHaveBeenCalledTimes(1);

    // pega o objeto que foi passado para salvar
    const usuarioPassadoParaSalvar = salvarSpy.mock.calls[0][0];

    // senha não pode ser texto plano
    expect(usuarioPassadoParaSalvar.senha).not.toBe('senha123');

    // hash deve validar com a senha original
    const ok = await bcryptjs.compare('senha123', usuarioPassadoParaSalvar.senha);
    expect(ok).toBe(true);

    // opcional: valida também o que seu service retorna
    expect(resultado.content.senha).toBe(usuarioPassadoParaSalvar.senha);
  });
});

Agora, sobre sua pergunta do mock ser “caixa branca”:

  • Fazer mock não obriga o teste a ser 100% caixa branca.
  • O que acontece é: para mockar um detalhe específico (ex.: Usuario.prototype.salvar), você está escolhendo um ponto de acoplamento da implementação. Isso adiciona um toque de caixa branca porque você conhece uma dependência interna.
  • A forma de reduzir esse acoplamento é mockar na borda, isto é, mockar a dependência (persistência) como um contrato, e continuar assertando o comportamento: “ao cadastrar, a senha enviada para persistência é hash”.

Regra prática:

  • Integração: deixa salvar real e usa banco (e aceita falhas por infraestrutura).
  • Unitário: mocka persistência e testa regra de negócio (hash, validações, fluxos de erro).

Fico à disposição!

Mas então para mockar precisamos saber que existe essa dependência interna. No nosso caso a dependência interna é o método "salvar" que é chamado dentro do nosso método "cadastrarUsuario". E para saber que existe essa dependência precisamos saber da implementação, ou seja, saber que o método "salvar" é chamado dentro do nosso método "cadastrarUsuario". Então como precisamos saber da implementação interna, então quando tem mock é caixa branca. Estou certo ou tem algo que não estou percebendo?

Oi, Luidi!

Gostei da sua linha de raciocinio e ela faz sentido, mas o ajuste importante é este: mock e caixa branca não são sinônimos.

Quando você faz mock de Usuario.prototype.salvar, realmente existe um componente de caixa branca, porque você precisou conhecer um detalhe interno da implementação do cadastrarUsuario: o fato de que ele chama salvar.

Mas isso não significa que todo teste com mock seja automaticamente caixa branca no sentido completo. O ponto central é:

tipo de teste responde o que está sendo isolado
caixa branca / caixa preta responde quanto da implementação interna o teste conhece

Então são duas classificações diferentes que podem se misturar.

No seu caso:

1. Se o teste chama cadastrarUsuario e deixa salvar real acessar banco, ele é teste de integração, porque depende da persistência real.

2. Se o teste chama cadastrarUsuario e mocka salvar, ele passa a ser teste unitário, porque a unidade cadastrarUsuario ficou isolada do banco.

3. Dentro desse teste unitário, se você mockou salvar sabendo que ele existe internamente, então esse teste tem um aspecto de caixa branca.

Ou seja, neste cenário, a classificação mais correta seria:

teste unitário com conhecimento de implementação interna

O que talvez esteja gerando a confusão é imaginar que um teste precisa ser ou unitário ou caixa branca. Na prática, ele pode ser:

unitário + caixa branca
unitário + caixa preta
integração + caixa preta
integração + caixa branca

O que muda é o critério analisado.

Neste ponto, a resposta direta para sua pergunta é:

sim, quando você mocka uma dependência interna específica que só descobriu olhando a implementação, existe sim um elemento de caixa branca.
Você percebeu corretamente isso.

Mas tem um detalhe importante: o mock não obriga o teste a ser caixa branca em qualquer nível. Isso depende do quanto o teste se acopla aos detalhes internos.

Exemplo:

  • Se você mocka exatamente Usuario.prototype.salvar porque viu esse método dentro do código, isso é mais próximo de caixa branca.
  • Se o código fosse estruturado para receber uma dependência externa, como um usuarioRepository, e o teste mockasse esse contrato externo, o teste continuaria sendo unitário, mas com menos acoplamento à implementação.

A diferença está no desenho do código.

No formato atual do seu método, para isolar o teste sem bater no banco, você realmente precisa conhecer que existem dependências internas como:

  • Usuario.pegarPeloEmail
  • usuario.salvar
  • bcryptjs.hash

Então, neste código específico, o teste unitário tende mesmo a ter um lado de caixa branca.

O resumo mais seguro é este:

Você está certo em dizer que, para mockar uma dependência interna, normalmente é necessário conhecer a implementação.
Mas a conclusão correta não é “mock = caixa branca sempre”. A conclusão correta é: mock pode introduzir caixa branca, enquanto o fato de o teste ser unitário depende de ele isolar ou não a unidade das dependências reais.

Fico à disposição!

1)Tu diz q o mock introduz um aspecto de caixa branca. Eu pensei que ou o teste é caixa branca ou preta. Então tem como o teste ser caixa preta com apecto de caixa branca por causa do mock? Pode dar um exemplo?

Oi, Luidi!

O ponto principal é este: caixa-preta e caixa-branca não precisam ser tratados como uma chave de liga/desliga. Na prática, o teste pode ser mais caixa-preta ou mais caixa-branca, dependendo do quanto ele conhece da implementação.

Quando você faz um teste chamando apenas cadastrarUsuario(data) e valida o resultado esperado, ele está em uma linha mais próxima de caixa-preta.

Quando, além disso, você decide mockar Usuario.prototype.salvar porque sabe que esse método é chamado internamente, o teste continua verificando o comportamento externo do método, mas agora ele usa um detalhe da implementação para se isolar. Então ele fica com um aspecto de caixa-branca.

Ou seja: não é contraditório dizer que um teste é unitário e, ao mesmo tempo, tem um grau de caixa-branca.

Veja a diferença:

Exemplo mais próximo de caixa-preta

  • você chama o método;
  • passa os dados de entrada;
  • valida a saída ou o erro;
  • sem verificar qual método interno foi chamado.
it('deve retornar erro quando a senha não for enviada', async () => {
  const data = {
    nome: 'John Doe',
    email: 'john@example.com'
  };

  await expect(authService.cadastrarUsuario(data))
    .rejects
    .toThrow('A senha de usuário é obrigatório!');
});

Neste caso, o teste não precisa saber se existe salvar, pegarPeloEmail ou qualquer outro método interno. Ele verifica só o contrato do método.

Agora veja um exemplo com mock, que traz um aspecto de caixa-branca:

it('deve criptografar a senha antes de salvar', async () => {
  const data = {
    nome: 'John Doe',
    email: 'john@example.com',
    senha: 'senha123'
  };

  jest.spyOn(Usuario, 'pegarPeloEmail').mockResolvedValue(null);

  const salvarSpy = jest
    .spyOn(Usuario.prototype, 'salvar')
    .mockImplementation(async function(usuario) {
      return {
        id: 1,
        nome: usuario.nome,
        email: usuario.email,
        senha: usuario.senha
      };
    });

  const resultado = await authService.cadastrarUsuario({ ...data });

  expect(salvarSpy).toHaveBeenCalledTimes(1);
  expect(resultado.content.senha).not.toBe('senha123');
});

Neste teste, para mockar salvar, você precisou saber que ele existe dentro da implementação. Então sim: existe conhecimento interno. Por isso ele tem um lado de caixa-branca.

A forma mais direta de pensar é esta:

Caixa-preta: testa o que entra e o que sai.
Caixa-branca: testa sabendo algo de dentro.
Mock: pode empurrar o teste para mais perto da caixa-branca, mas não transforma automaticamente todo teste com mock em um teste 100% caixa-branca.

Então, respondendo objetivamente à sua pergunta: sim, um teste pode continuar validando comportamento externo e, ao mesmo tempo, ter um aspecto de caixa-branca por causa do mock. Isso acontece porque as classificações não competem entre si; elas analisam o teste por ângulos diferentes.

Fico à disposição!