O que é Mocking e Por que é Importante?

Em testes de unidade e integração, é comum trabalhar com objetos e métodos que dependem de outras partes do sistema. No entanto, em vez de chamar diretamente essas dependências reais, é benéfico simular o comportamento delas. Isso é chamado de “mocking” ou “stubbing”. Nesse artigo, pretendo revisar os aspectos básicos…

Em testes de unidade e integração, é comum trabalhar com objetos e métodos que dependem de outras partes do sistema. No entanto, em vez de chamar diretamente essas dependências reais, é benéfico simular o comportamento delas. Isso é chamado de “mocking” ou “stubbing”.

Nesse artigo, pretendo revisar os aspectos básicos e aprofundar em 4 métodos de Rspec relevantes ao tema

Simulando Comportamento com RSpec

O RSpec oferece uma sintaxe conveniente para simular métodos em objetos durante os testes. Por exemplo, podemos usar a seguinte sintaxe:

allow(objeto).to receive(:metodo).and_return('Mocked Result')

Aqui, allow(objeto).to receive(:metodo) simula o método metodo no objeto objeto para que ele retorne 'Mocked Result' em vez de executar a lógica real do método.

Por que Simular é Diferente de Chamar o Método?

A simulação de métodos é diferente de chamar o método diretamente em alguns aspectos importantes:

  1. Isolamento de Dependências: Ao simular métodos, você isola as dependências do objeto sendo testado. Isso significa que você não precisa se preocupar com o estado ou o comportamento real das dependências externas. Em vez disso, você pode se concentrar no comportamento específico do objeto em teste.
  2. Controle do Comportamento: A simulação permite que você controle o comportamento de métodos de acordo com suas necessidades de teste. Você pode especificar o que um método deve retornar, lançar exceções ou até mesmo chamar blocos.
  3. Evitar Efeitos Colaterais: Chamar métodos reais pode causar efeitos colaterais não desejados em um ambiente de teste. Ao simular métodos, você evita esses efeitos colaterais e mantém seus testes previsíveis e consistentes.

Exemplo de Quando a Simulação é Melhor

Considere um cenário em que você está testando uma função que faz uma chamada a uma API externa para buscar dados. Chamar essa API real em seus testes pode introduzir inconsistências devido à disponibilidade da API externa, à taxa de chamadas ou à volatilidade dos dados.

class MeuServico
  def buscar_dados_da_api
    # Lógica para chamar a API externa
    # e processar os dados
  end
end

Em vez de fazer chamadas reais à API externa em seus testes, você pode simular o comportamento do método buscar_dados_da_api usando allow e and_return. Isso permite que você controle os dados de retorno e garanta que seus testes não dependam da disponibilidade ou do estado da API externa.

RSpec.describe MeuServico do
  it 'busca de dados da API externa' do
    meu_servico = MeuServico.new

    # Simulamos o comportamento da chamada da API
    allow(meu_servico).to receive(:buscar_dados_da_api).and_return('Dados Simulados')

    resultado = meu_servico.buscar_dados_da_api

    expect(resultado).to eq('Dados Simulados')
  end
end

Neste exemplo, estamos simulando a chamada da API, garantindo que nossos testes sejam independentes da API real. Isso torna seus testes mais previsíveis e confiáveis, especialmente em cenários onde você não tem controle direto sobre recursos externos.

Em resumo, a simulação de métodos é uma prática valiosa em testes, permitindo que você isole dependências, controle o comportamento e evite efeitos colaterais indesejados, tornando seus testes mais confiáveis e robustos. Outras formas de uso:

allow(objeto).to receive(:executar_com_bloco).and_yield(Mocked Argument')

allow(objeto).to receive(:metodo_com_excecao).and_raise(StandardError, 'Mensagem de Erro')

allow(objeto).to receive(:calcular_resultado) do |arg|
  arg * 3
end

receive_message_chain: Encadeando Expectativas em Métodos

Às vezes, ao escrever testes, nos deparamos com objetos que têm métodos encadeados, ou seja, chamam vários métodos um após o outro. O RSpec nos oferece uma maneira conveniente de verificar essas chamadas encadeadas usando receive_message_chain.

Como Funciona

receive_message_chain permite que você encadeie expectativas em chamadas de método. Você pode usá-lo quando deseja verificar não apenas se um método foi chamado, mas também se os métodos subsequentes nesse encadeamento também foram chamados.

Exemplo

Suponha que temos as seguintes classes:

class CarrinhoDeCompras
  def self.iniciar
    # Alguma lógica de inicialização
  end

  def adicionar_item(item)
    # Adicionar item ao carrinho
  end

  def finalizar_compra
    # Finalizar a compra
  end
end

Agora, vamos escrever um teste que usa receive_message_chain:

RSpec.describe CarrinhoDeCompras do
  it 'verifica chamadas encadeadas de métodos' do
    carrinho = CarrinhoDeCompras.iniciar
    item = double('Item')

    expect(carrinho).to receive_message_chain(:adicionar_item, :finalizar_compra)

    carrinho.adicionar_item(item)
    carrinho.finalizar_compra
  end
end

Neste exemplo, estamos verificando se o método adicionar_item foi chamado em um objeto carrinho e, em seguida, se o método finalizar_compra também foi chamado em seguida. Usando receive_message_chain, podemos encadear essas expectativas em uma única linha de código.

Isso pode ser especialmente útil quando você deseja testar o fluxo completo de uma sequência de métodos em um objeto e garantir que eles sejam chamados na ordem correta. Lembre-se de que, embora receive_message_chain possa simplificar o teste de chamadas encadeadas, também pode tornar seus testes menos explícitos. Portanto, use-o com moderação e em situações apropriadas onde a clareza do teste não seja comprometida.

Outra forma de uso:

allow(user).to receive_message_chain(:profile, :full_name).and_return("John Doe")
# Equivale a fazer user.profile.full_name e definir o retorno desejado

Validando Respostas da API com match_response_schema

Quando você está desenvolvendo e testando uma API, uma das preocupações fundamentais é garantir que as respostas da API estejam corretas e sigam o formato esperado. Para alcançar isso, uma abordagem eficaz é usar o match_response_schema, um recurso oferecido pelo framework de testes RSpec, que permite validar se as respostas estão em conformidade com um esquema predefinido.

JSON Schema e match_response_schema

Para começar, vamos explorar como usar o match_response_schema com respostas JSON. Primeiro, é importante entender o conceito de JSON Schema, que é uma especificação que descreve a estrutura e os tipos de dados esperados em um documento JSON. Usando JSON Schema, você pode definir regras detalhadas para cada campo em sua resposta JSON.

Exemplo de JSON Schema: Considere um esquema JSON simples para representar informações de um usuário:

{
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "nome": { "type": "string" },
    "email": { "type": "string", "format": "email" }
  },
  "required": ["id", "nome", "email"]
}

No exemplo acima, estamos especificando que um objeto JSON deve conter três campos: “id” (um número inteiro), “nome” (uma string) e “email” (uma string no formato de e-mail). Além disso, todos esses campos são obrigatórios. Agora, vejamos como usar o match_response_schema com esse esquema para validar uma resposta JSON:

JSON_SCHEMA_PATH = 'path/to/user_schema.json'

RSpec.describe 'API de Usuários' do
  it 'retorna um usuário válido em JSON' do
    response = {
      "id": 1,
      "nome": "Alice",
      "email": "[email protected]"
    }
    expect(response).to match_response_schema(JSON_SCHEMA_PATH)
  end
end

Neste teste, estamos assumindo que já configuramos o uso do match_response_schema para validar respostas JSON. O matcher ‘usuario’ está vinculado ao esquema JSON que definimos anteriormente. Se a resposta JSON corresponder ao esquema, o teste passará.

match_response_schema com XML

O match_response_schema não se limita apenas a respostas JSON. Você também pode usá-lo para validar respostas em outros formatos, como XML. Para isso, você precisa criar um esquema XML correspondente e ajustar o matcher de acordo.

Exemplo de XML Schema: Suponha que você esteja trabalhando com respostas XML e deseja validar um esquema para um elemento de pedido:

<pedido>
  <cliente>
    <nome>Alice</nome>
    <email>[email protected]</email>
  </cliente>
  <itens>
    <!-- Detalhes dos itens aqui -->
  </itens>
</pedido>

Neste caso, você pode criar um esquema XML que descreve a estrutura acima e, em seguida, configurar o match_response_schema para usá-lo.

RSpec.describe 'API de Pedidos' do
  it 'retorna um pedido válido em XML' do
    # Generate the XML response using the method
    response = generate_pedido_xml('Alice', '[email protected]')

    expect(response).to match_response_schema('pedido')
  end
end

Neste teste, estamos usando um documento XML e o matcher ‘pedido’ para validar se a resposta XML está em conformidade com o esquema definido para pedidos. Lembre-se de que, ao trabalhar com esquemas XML, é importante garantir que a resposta e o esquema estejam devidamente configurados e que o matcher esteja ajustado para o formato correto.

Testando Tarefas Enfileiradas com have_enqueued_job em RSpec

À medida que as aplicações web se tornam mais complexas, a necessidade de executar tarefas em segundo plano se torna essencial. Uma das ferramentas mais populares para gerenciar tarefas enfileiradas em Ruby on Rails é o Sidekiq, que permite executar tarefas assíncronas de maneira eficiente. No entanto, testar o comportamento dessas tarefas em segundo plano pode ser desafiador. É aqui que o have_enqueued_job do RSpec entra em jogo.

O que é have_enqueued_job?

have_enqueued_job é um matcher poderoso fornecido pelo RSpec que permite testar se uma tarefa foi enfileirada em um worker do Sidekiq ou outra biblioteca de tarefas em segundo plano. Isso é útil quando você deseja verificar se uma ação desencadeou a execução de uma tarefa em segundo plano.

Exemplo de Uso

Vamos considerar um cenário em que temos uma aplicação Rails que envia e-mails de boas-vindas aos novos usuários após o registro. A tarefa de envio de e-mail é tratada por um worker do Sidekiq. Vamos escrever um teste usando have_enqueued_job para garantir que o envio de e-mail tenha sido enfileirado corretamente:

RSpec.describe 'Registro de Usuário' do
  it 'enfileira uma tarefa para enviar e-mail de boas-vindas' do
    # Simulando o registro de um novo usuário
    user = User.create(nome: 'Alice', email: '[email protected]')

    # Verifica se a tarefa de envio de e-mail foi enfileirada
    expect {
      WelcomeEmailWorker.perform_async(user.id)
    }.to have_enqueued_job(WelcomeEmailWorker)

  end
end

# Ou personalizada com argumentos

expect {
  WelcomeEmailWorker.perform_async(user.id)
}.to have_enqueued_job(WelcomeEmailWorker).with(user.id, 'welcome')

Neste exemplo, estamos criando um novo usuário e, em seguida, enfileirando a tarefa WelcomeEmailWorker para enviar um e-mail de boas-vindas. Usando have_enqueued_job, podemos verificar se essa tarefa foi corretamente enfileirada.

Conclusão

Em resumo, ao trabalhar com testes em Ruby on Rails, é fundamental contar com ferramentas e recursos que simplifiquem a validação de comportamentos específicos e garantam a confiabilidade de sua aplicação. No decorrer deste artigo, exploramos quatro métodos essenciais do RSpec que podem aprimorar significativamente a eficácia de seus testes:

  1. receive_message_chain: Permite verificar se uma sequência de métodos em um objeto é chamada na ordem correta, simplificando a validação de interações complexas.
  2. have_enqueued_job: Torna mais fácil testar tarefas enfileiradas em bibliotecas como o Sidekiq, garantindo que as ações desencadeiem a execução das tarefas em segundo plano esperadas.
  3. match_response_schema: Oferece uma maneira sólida de validar respostas de API em relação a esquemas predefinidos, promovendo consistência e qualidade em suas APIs.
  4. allow(obj).to receive(:method_name).and_return('Stubbed Result'): Facilita a simulação de chamadas a métodos reais durante os testes, isolando dependências e tornando os testes mais previsíveis.

Integrar esses métodos em sua caixa de ferramentas de testes pode ajudar a garantir que sua aplicação funcione conforme o esperado, mesmo em situações complexas. Ao usar essas técnicas, você está no caminho certo para criar testes robustos e confiáveis que contribuem para a qualidade e a estabilidade de seu projeto Ruby on Rails.

0 Comentário