Ruby

9 nov, 2018

10 dicas para facilitar o uso da gem VCR nos testes da sua app Ruby

Publicidade

A gem VCR é uma ótima alternativa para fazermos testes integrados em nossas apps Ruby. Ela também pode ser utilizada em outras linguagens, mas isso não é o foco deste artigo.

Ela nos permite automatizar o processo de stub das requisições web através da gem Webmock (ou de alguma outra semelhante). Para isso, são gravados cassettes com todas as requisições e respostas feitas para APIs externas. Com isso, podemos executar a suite de testes com mais velocidade e sem depender do estado e disponibilidade destas.

Porém, quando a suite de testes começa a ficar grande, precisamos tomar alguns cuidados para que a manutenção dela não se torne um pesadelo.

Vou listar algumas dicas que podem deixar nosso dia a dia mais fácil.

Os exemplos estão usando a gem rspec em um projeto rails, mas o VCR pode ser utilizado em outros frameworks, como por exemplo sinatra com minitest.

1. Configure o VCR para gerar o nome dos cassettes automaticamente

Para não utilizar o VCR.use_cassette em todos os cenários e ainda ter que nomear os cassettes, caso esteja usando o rspec, é possível marcar cada cenário que irá utilizar vcr com o símbolo :vcr, como no exemplo abaixo:

describe SomeApi do
it 'creates the product', :vcr do
# test...
end
end

Para isso, basta utilizar a seguinte configuração do vcr:

VCR.configure do |c|
c.configure_rspec_metadata!
end

Deste jeito, será gerado um arquivo de cassete de acordo com o contexto atual, por exemplo, spec/fixtures/vcr_cassettes/SomeApi/creates_the_product.yml

2. Configure o VCR para gravar os cassettes somente 1 vez

O vcr tem alguns modos de gravação de cassettes. O recomendado é utilizar o :once que irá gravar o arquivo somente se ele não existir.

VCR.configure do |c|
vcr_mode = :once
end

Depois da gravação, caso algum teste tente chamar as apis externas com dados diferentes dos que foram gravados no cassette, o teste irá quebrar, como no exemplo abaixo:

VCR::Errors::UnhandledHTTPRequestError:

================================================================================
An HTTP request has been made that VCR does not know how to handle:
GET http://someapi.com?lala=popo
Body:

VCR is currently using the following cassette:
- /home/fabioperrella/workspace/some-app/spec/fixtures/vcr_cassettes/some_api/some_vcr_.yml
- :record => :once
- :match_requests_on => [:method, :uri, :body]

A vantagem desta abordagem é ter certeza de que os cassettes são suficientes para rodarem todos os testes e nos habilitar a fazer a próxima dica, que é desabilitar as requisições externas.

3. Proíba as requisições externas

Poder rodar a suite de testes sem depender da disponibilidade e estado da apps externas, de estar conectado à internet ou até de uma VPN é o nosso objetivo.

Este já é o comportamento padrão das últimas versões do VCR, que pode ser alterado através da conf allow_http_connections_when_no_cassette, mas não recomendo.

Para que a suite de testes chegue nesse ponto, esta dica precisa andar em paralelo com a dica anterior.

4. Permita que um cassette seja regravado facilmente

Às vezes, precisamos fazer mudanças em algum cenário de teste que já tenha um cassette gravado e pode ser necessário regravá-lo.

O jeito mais simples seria descobrir onde está esse arquivo, deletá-lo e regravá-lo.

Mas podemos usar a configuração abaixo, para que, através de uma variável de ambiente, possamos sinalizar que o cassette deve ser regravado:

VCR.configure do |c|
vcr_mode = ENV['VCR_MODE'] =~ /rec/i ? :all : :once

c.default_cassette_options = {
record: vcr_mode,
match_requests_on: %i[method uri body]
}
end

Com isso, quando quiser regravar um VCR, basta rodar o teste assim:

VCR_MODE=rec bundle exec rspec spec/some_class_spec.rb:30

Mas cuidado, se você rodar todos os testes, irá regravar todos os cassetes!

5. Utilize o VCR.current_cassette.file para descobrir onde está gravado o cassette

Utilizar o :vcr para habilitar o vcr em um cenário nos facilita na tarefa de nomear e organizar os cassettes, porém dificulta na hora de queremos saber onde está o arquivo do cassette.

Para ajudar, é possível utilizar o método abaixo:

it 'does somethid', :vcr do
puts VCR.current_cassette.file
# test ...
end

Dica extra: tenha um snippet no seu editor de texto preferido para gerar essa linha inteira. Eu tenho o meu do sublime aqui.

6. Cuidado com as sequences das factories

Quando usamos a gem factory_bot, temos a possibilidade de criar sequences para os atributos.

Essas sequences podem gerar valores diferentes para cada cenário de teste, dependendo da ordem que eles rodarem.

É recomendado que a suite de testes rode os cenários com ordem randômica, por isso, nos testes que utilizarem factories com sequence + vcr, recomenda-se que sejam fixados os valores que seriam gerados pela sequences, para que o payload gravado nos cassettes não mude, como no exemplo abaixo:

#spec/factories/cars.rb
FactoryBot.define do
factory :product do
description "some product"
sequence(:sku) { |n| "SKU-#{n}" }
end
end

#spec/car_api_spec.rb
describe CartCreation do
it "add the product to the cart", :vcr do
product = build(:product, sku: 'SKU-33') # se não fixar o sku, o VCR do teste pode quebrar!
response = CartCreation.create(product)

expect(response).to eq(:ok)
end
end

7. Deixe o teste preparado para ser regravado

Em algumas situações, podemos querer regravar alguns cassettes. Se o setup do teste não estiver preparado, isso pode se tornar uma tarefa difícil.

Como exemplo, se estamos testando uma api que vai deletar um recurso no servidor:

it 'deletes the resource', :vcr
resource_id = 40 # ID que ja existe em algum lugar

response = SomeApi.delete(resource_id)

expect(response).to eq(:o)
end

Na primeira vez que rodarmos esse teste, o cassette será gravado e vai funcionar. Mas caso precisemos regravar, o recurso não irá mais existir e o teste vai falhar. Um jeito de deixar este cenário idempotente é de criar o recurso no setup, assim:

it 'deletes the resource', :vcr
# setup
resource = SomeApi.create

# exercise
response = SomeApi.delete(resource.id)

# verify
expect(response).to eq(:ok)
end

8. Cuidado com os caches e a ordem de execução

Uma boa prática na suite de testes é fazer com o os cenários rodem em ordem aleatória. No rspec é possível fazer isso com a seguinte configuração:

RSpec.configure do |config|
config.order = 'random'
end

Para isso, cada cenário deve ser independente dos outros.

Quando usamos o vcr do jeito que foi sugerido neste artigo, com a configuração record: :once e com o allow_http_connections_when_no_cassette=false, qualquer requisição que foi feita diferente do que estava gravado, irá quebrar o teste.

Um exemplo de problema que pode acontecer com cache, é fazermos cache de algum token de autenticação de api.

Com isso, o primeiro cassette gravado vai ter as requisições para gerar esse token, mas os próximos não. Assim:

# primeiro cassette
POST http://some-api.com/authentication
GET http://some-api.com/users?token=1234

# segundo cassette
GET http://some-api.com/products?token=1234

Se o teste com o segundo cassette for executado primeiro, ele irá quebrar, pois haverá uma requisição de POST para gerar o token de autenticação.

Desligando o cache do token, ficaria assim:

# primeiro cassette
POST http://some-api.com/authentication
GET http://some-api.com/users?token=1234

# segundo cassette
POST http://some-api.com/authentication
GET http://some-api.com/products?token=3456

Com isso, conseguimos rodar os testes em qualquer ordem.

9. Filtre as urls das apis na gravação dos cassettes

Para evitar que os testes quebrem ou que precisemos regravar todos os cassettes quando a url de alguma api mudar, podemos usar a seguinte configuração para normalizar as urls:

VCR.configure do |c|
c.filter_sensitive_data("<SOME_API>") { 'some-api.com.br' }
end

Se tivermos as urls em variáveis de ambiente, podemos fazer ainda melhor:

VCR.configure do |c|
%w[
SOME_API_URL
OTHER_API_URL
].each do |key|
c.filter_sensitive_data("<#{key}>") { ENV[key] }
end
end

Com isso, conseguimos facilmente trocar a url de uma api no .env sem quebrar os testes!

10. Ignore o diff dos cassettes nos merge requests

Sempre que criamos um merge request (ou pull request) precisamos nos preocupar em não deixar o diff grande para facilitar o review das pessoas.

Em algumas situações, uma pequena mudança no código pode fazer com que muitos cassettes precisem ser regravados.

Para evitar que isso polua o diff do MR, é possível utilizar a configuração do git abaixo. Basta criar o arquivo .gitattributes:

* text=auto

spec/fixtures/vcr_cassettes/**/* -diff

O Gitlab e o Github já suportam esta configuração e, com isso, irão ocultar o diff destes arquivos.

Atenção: o diff não aparecerá inclusive no client de git da máquina de desenvolvimento. Caso precise disto algum dia, é necessário apagar este arquivo temporariamente.

Fechando

Espero que essas dicas ajudem a facilitar a manutenção dos seus testes com vcr.

Se tiverem alguma sugestão ou mais dicas, deixem no comentário.

Até uma próxima!