13
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?

13 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!

Tem como um teste de caixa preta ter aspectos de caixa branca por causa dos mocks, mas tem como o teste de caixa branca ter aspectos de caixa preta?

Olha se está certo ou errado:

O teste de caixa preta pode ser 100% preta quando é um teste de integração, pois não usamos mocks.
O teste de caixa preta pode ter aspectos de caixa branca quando quando o teste é unitário e fazemos mocks.
O teste unitário é sempre de caixa branca 100% sem aspectos de caixa preta.
O teste de integração é 100% de caixa preta ou majoritariamente preta mas com aspectos brancos por causa dos mocks.

Pra terminar esse tópico. Faz um resuminho na forma q eu fiz acima corrigindo as frases e/ou colocando outras. Use exemplo pra mostrar oq cada frase quer dizer.

Olá, Luidi!

Sobre sua última dúvida, o melhor jeito de fechar esse assunto é separar duas classificações diferentes:

  • teste unitário vs teste de integração → isso depende de isolar ou não dependências reais.
  • caixa preta vs caixa branca → isso depende de quanto o teste conhece da implementação.

Então, um teste não precisa ser 100% uma coisa só em todos os sentidos. Ele pode ser, por exemplo, unitário e ao mesmo tempo ter um grau de caixa branca.

Resumo corrigido no formato que você montou:

1. Um teste pode ser mais caixa preta ou mais caixa branca; isso não precisa ser absoluto.
Exemplo: se você chama cadastrarUsuario(data) e valida só o retorno ou o erro, sem olhar métodos internos, ele está mais para caixa preta.

2. Um teste de integração costuma ser mais próximo de caixa preta, porque usa dependências reais.
Exemplo: deixar Usuario.pegarPeloEmail() e salvar() acessarem o banco de verdade.
Você testa o fluxo completo sem substituir partes internas.

3. Um teste unitário não é sempre 100% caixa branca.
Exemplo: testar que cadastrarUsuario lança erro quando senha não foi enviada:
você valida só entrada e saída, sem precisar saber se existe salvar, hash ou pegarPeloEmail.
Isso é unitário e pode ser bem próximo de caixa preta.

4. Um teste unitário pode ter aspectos de caixa branca quando usa mock de uma dependência interna conhecida.
Exemplo: mockar Usuario.prototype.salvar.
Neste caso, para isolar a unidade, você precisou saber que cadastrarUsuario() chama salvar() internamente.
Então ele continua sendo unitário, mas com aspecto de caixa branca.

5. Mock não significa automaticamente caixa branca total.
Exemplo: às vezes você mocka uma dependência externa conhecida pelo contrato, sem acoplar o teste a muitos detalhes internos.
Quanto mais o teste depende de saber quem é chamado internamente, mais ele anda para a caixa branca.

6. Teste de integração normalmente não usa mock, mas pode usar em alguns cenários e continuar sendo integração.
Exemplo: testar service + banco real, mas mockar um envio de e-mail externo.
Ele continua sendo integração, porque ainda integra partes reais do sistema, só não todas.

7. Então o mais seguro é pensar em “grau” e não em rótulo rígido.
Um teste pode ser:

  • unitário + mais caixa preta
  • unitário + mais caixa branca
  • integração + mais caixa preta
  • integração + com algum aspecto de caixa branca

Aplicando ao seu caso:

  • Sem mock e com banco realteste de integração, mais próximo de caixa preta.
  • Com mock de salvar() e pegarPeloEmail()teste unitário, com aspectos de caixa branca.
  • Testando só validação de entrada, como ausência de senha → teste unitário, bem próximo de caixa preta.

Veja este exemplo de teste unitário mais próximo de caixa preta:

it('deve lançar 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 que existe salvar() nem pegarPeloEmail().

Veja este exemplo de teste unitário com 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 exemplo, para mockar salvar, você precisou conhecer a implementação.
Por isso ele tem aspecto de caixa branca.

Fechando em uma frase:
tipo de teste diz o que foi isolado; caixa preta/branca diz quanto da implementação o teste conhece.

Fico à disposição e peço perdão pela demora em responder!