Como configurar ambiente de testes em Ruby on Rails com RSpec
Tags: ruby, rails, testing
Published: 2021-04-21 17:31:03

> Photo by [David Travis](https://unsplash.com/photos/WC6MJ0kRzGw) on [Unsplash](https://unsplash.com)

Olá, nesse post eu vou mostrar como configurar o RSpec e demais gems para dar suporte a um ambiente de testes robusto no Ruby on Rails.

Se quiser ver o resultado final das instalações com todos os testes criados, acesse o meu repositório:

{% github peimelo/blog_api no-readme %}

Esse post serve para dar apoio ao meu curso de **Ruby on Rails 6 - Autenticação via API**, que está disponível no YouTube:

{% youtube i6vul6MqylE %}

# Criando o projeto

Vamos criar um novo projeto com banco de dados PostgreSQL e servindo como API (rode no Terminal):

```bash
$ rails new blog_api -d postgresql --api -T
```

Flags:

* `-d postgresql` configura o banco PostgreSQL, você pode omitir essa flag caso queira usar o SQLite, que é o banco padrão;
* `--api` gera o projeto como API, se omitir essa flag será criado como MVC;
* `-T` deixa de gerar os arquivos de testes padrão do Rails que é o Minitest. Caso você já tenha um projeto criado, pode apagar a pasta `test`.

# Adicionando o RSpec

O [RSpec](https://github.com/rspec/rspec-rails) é uma opção ao Minitest que vem como padrão no Ruby on Rails. Ele é muito usado e oferece uma sintaxe de fácil compreensão.

Vamos adicionar o [RSpec](https://github.com/rspec/rspec-rails) via bundle (ao invés de editar diretamente o Gemfile), porque com esse comando ele adiciona a última versão da gem (rode no Terminal):

```bash
$ bundle add rspec-rails -g 'development, test'
```

Agora rode o comando para criar os arquivos iniciais:

```bash
$ rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb
```

Vamos atualizar o arquivo `.rspec` para ficar dessa forma:

```spec
--require spec_helper
--format documentation
```

Na primeira linha ele importa o arquivo `spec/spec_helper.rb` nos testes e o outro comando ele exibe no terminal as mensagens dos testes dessa forma:

![Screen Shot 2021-04-18 at 10.01.15](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h6m7crfwjbo55qgodbrk.png) 

ao invés de pontinhos por arquivo:

![Screen Shot 2021-04-13 at 18.20.24](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/909ichidnh4b7awupi1e.png) 

Crie uma pasta chamada `support` dentro da pasta `spec`. Agora vamos no arquivo `spec/rails_helper.rb` e **descomente** a linha abaixo para que o [RSpec](https://github.com/rspec/rspec-rails) importe tudo que estiver dentro da pasta `spec/support` que vamos precisar mais abaixo nas gems [Factory Bot](https://github.com/thoughtbot/factory_bot_rails) e [Database Cleaner](https://github.com/DatabaseCleaner/database_cleaner):

```ruby
# ... código existente

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

# ... código existente
```

# Adicionando o SimpleCov

O [SimpleCov](https://github.com/simplecov-ruby/simplecov) mostra a cobertura de código dos nossos testes que é um indicador importante. Vamos adicionar também o `simplecov_json_formatter`, pois vamos precisar dele para integrar o projeto ao site [Code Climate](https://codeclimate.com/)

Rode o comando para adicionar a gem:

```bash
$ bundle add simplecov -g 'test' --require false
$ bundle add simplecov_json_formatter -g 'test' --require false
```

Vamos configurá-lo no projeto, adicionando no início do arquivo `spec/rails_helper.rb` o trecho de código abaixo:

```ruby
require 'spec_helper'
require 'simplecov'
require 'simplecov_json_formatter'

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
  SimpleCov::Formatter::JSONFormatter,
  SimpleCov::Formatter::HTMLFormatter
])

SimpleCov.start do
  add_group 'Config', 'config'
  add_group 'Controllers', 'app/controllers'
  add_group 'Libs', 'lib'
  add_group 'Models', 'app/models'
  add_group 'Serializers', 'app/serializers'
  add_group 'Specs', 'spec'
end

# ... resto do código existente
```

Aqui estamos fazendo o import e iniciando o [SimpleCov](https://github.com/simplecov-ruby/simplecov), porém eu defini grupos para melhorar a visualização do resultado por abas. Se você desejar poderá adicionar/remover grupos. O resultado final ficará parecido com isso:

![Screen Shot 2021-04-19 at 19.38.24](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2sjmeqcgxl6in2vusfd.png) 

Após rodarmos os testes o [SimpleCov](https://github.com/simplecov-ruby/simplecov) irá gerar um relatório que ficará na pasta `coverage` do nosso projeto, porém é desnecessário que ela esteja no controle de versão do GIT, por isso vamos adicionar uma linha no arquivo `.gitignore` para ignorá-la:

```ruby
# ... código existente

/coverage
```

# Adicionando o Shoulda Matchers

O [Shoulda Matchers](https://github.com/thoughtbot/shoulda-matchers) ajuda a fazer alguns testes em apenas uma linha para funcionalidades comuns, que se fossem escritas na mão seriam complexas e sujeitas a erro.

Rode o comando abaixo para adicionar a gem para o group test:

```bash
$ bundle add shoulda-matchers -g 'test'
```

Adicione o código abaixo no final do arquivo `spec/rails_helpers.rb` e antes do último `end`:

```ruby
# ... código existente

  Shoulda::Matchers.configure do |config|
    config.integrate do |with|
      with.test_framework :rspec
      with.library :rails
    end
  end

end # último end do arquivo spec/rails_helpers.rb
```

# Adicionando o Factory Bot

O [Factory Bot](https://github.com/thoughtbot/factory_bot_rails) facilita a fabricação de dados para testes, podendo ser usada para pegar os atributos com valores, criar uma nova instância ou mesmo salvar no banco de dados. Rode o comando para adicionar a gem:

```bash
$ bundle add factory_bot_rails -g 'development, test'
```

Agora vamos configurar a nossa suite de testes para adicionar os métodos do factory_bot, para isso crie um arquivo chamado  `spec/support/factory_bot.rb` e cole nele o trecho abaixo:

```ruby
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
```

Dessa forma, ao invés de usarmos nos arquivos de teste o comando `FactoryBot.build :article`, vamos usar apenas `build :article`.

# Adicionando o Faker

O [Faker](https://github.com/faker-ruby/faker) serve para gerar dados fictícios de diversos tipos. Rode o comando para instalar:

```bash
$ bundle add faker -g 'development, test'
```

# Adicionando o Database Cleaner

O [Database Cleaner](https://github.com/DatabaseCleaner/database_cleaner) apaga os registros do banco de dados de testes antes de rodar os testes. Para instalar:

```bash
$ bundle add database_cleaner-active_record -g 'test'
```

Agora vamos configurar a nossa suite de testes para adicionar os métodos do [Database Cleaner](https://github.com/DatabaseCleaner/database_cleaner), para isso crie o arquivo `spec/support/database_cleaner.rb` e cole o trecho abaixo:

```ruby
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
end
```

# Organizando o Gemfile

Como já adicionamos todas as gems necessárias, eu dou uma organizada no `Gemfile` para cada uma ficar no seu próprio grupo. Conforme dito antes, eu uso o comando `bundle add` para que ele adicione a versão da gem ao invés de ficar sem nada.

Veja como fica a organização das gems no `Gemfile`:

```ruby
# ... código existente

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem "factory_bot_rails", "~> 6.1"
  gem "faker", "~> 2.17"
  gem "rspec-rails", "~> 5.0"
end

group :test do
  gem "database_cleaner-active_record", "~> 2.0"
  gem "shoulda-matchers", "~> 4.5"
  gem "simplecov", "~> 0.21.2", require: false
end

# ... código existente
```

Veja que retirei os trechos `:group => :test` e `:groups => [:development, :test]` após cada gem ficar no seu grupo.

# Adicionando Helpers

Podemos adicionar métodos para simplificar operações e evitar repetições. Tem um helper que eu uso que serve para acessar a resposta json nos meus testes de requests. 

Crie um arquivo chamado `spec/support/request_helpers.rb` e adicione o conteúdo:

```ruby
module Request
  module JsonHelpers
    def json_response
      @json_response ||= JSON.parse(response.body, symbolize_names: true)
    end
  end
end
```

Vamos alterar novamente o arquivo `spec/rails_helper.rb` para adicionar depois do `end` da configuração do `Shoulda::Matchers` e antes do `end` final do arquivo a informação para incluir o `Request::JsonHelpers`:

```ruby
# ... código existente

  Shoulda::Matchers.configure do |config|
    config.integrate do |with|
      with.test_framework :rspec
      with.library :rails
    end
  end

  config.include Request::JsonHelpers, type: :request
end # último end do arquivo spec/rails_helpers.rb
```

Dessa forma, podemos usar o helper nos testes de request como no exemplo abaixo (veja no arquivo `spec/requests/api/v2/articles_spec.rb` que se encontra no repositório de exemplo citado no início desse post):

```ruby
# ... código existente

  describe 'GET /index' do
    context 'without logged user' do
      it 'renders two articles from distinct users' do
        article
        article_two

        get api_articles_url, headers: {}, as: :json
        expect(json_response.size).to eq 2
      end
    end
  end

# ... código existente
```

# Integrando tudo

Agora que tudo foi configurado, qualquer comando **rails generate** irá criar o arquivo de testes correspondente.

Vamos criar um CRUD de Articles para testar apenas o model como exemplo. Rode o comando:

```bash
$ rails g scaffold Article title:string body:text
```

Caso você já tenha um projeto existente e está adicionando os testes nesse momento, segundo a documentação do [RSpec](https://github.com/rspec/rspec-rails), você pode rodar o comando abaixo para criar os arquivos de testes do model:

```bash
$ rails g rspec:model Article
      create  spec/models/article_spec.rb
      invoke  factory_bot
      create    spec/factories/articles.rb
```

Temos que rodar o migrate, tanto para `development` quanto para `test`, com os dois comandos:

```bash
$ rails db:migrate

$ rails db:migrate RAILS_ENV=test
```

## Geração de dados fictícios

Vamos atualizar o arquivo que gera os dados de Article, edite o arquivo `spec/factories/articles.rb`:

```ruby
FactoryBot.define do
  factory :article do
    title { Faker::Lorem.sentence }
    body { Faker::Lorem.paragraph(sentence_count: 10) }
  end
end
```

Dessa forma o [Factory Bot](https://github.com/thoughtbot/factory_bot_rails) e [Faker](https://github.com/faker-ruby/faker) irão gerar dados fictícios e aleatórios para serem testados quando forem solicitados.

## Testando o model Article

Vamos atualizar o arquivo `app/models/article.rb` para adicionar algumas validações:

```ruby
class Article < ApplicationRecord
  validates :title, presence: true,
                    length: { minimum: 3 },
                    uniqueness: { case_sensitive: false }
  validates :body, presence: true
end
```

Agora vamos criar nossos testes modificando o arquivo `spec/models/article_spec.rb`:

```ruby
require 'rails_helper'

RSpec.describe Article, type: :model do
  subject(:article) { build :article }

  describe 'validations' do
    it { should validate_presence_of(:title) }
    it { should validate_presence_of(:body) }

    it { should validate_length_of(:title).is_at_least(3) }

    it { should validate_uniqueness_of(:title).case_insensitive }
  end
end
```

Agora rode no terminal o comando `rspec` e veja o resultado:

![Screen Shot 2021-04-18 at 10.01.15](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3tkyieqzfvqpe2a6ci7l.png)
 
Percebe que o [Shoulda Matchers](https://github.com/thoughtbot/shoulda-matchers) adiciona o texto referente aos testes e cada teste fica com apenas 1 linha.

Outro resultado do comando `rspec` é que o [SimpleCov](https://github.com/simplecov-ruby/simplecov) vai gerar o relatório de cobertura de testes em `coverage\index.html`. Dê um duplo clique nesse arquivo no seu gerenciador de arquivos (Windows Explorer/Finder) e veja o resultado:

![Screen Shot 2021-04-19 at 19.41.01](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nafzzo2wi60g4ac0e6nd.png)
 
# Bônus: Teste de integração via GitHub Actions

Eu comecei a usar o [GitHub Actions](https://docs.github.com/pt/actions) ao invés do [CircleCI](https://circleci.com) para rodar meus testes antes de fazer o deploy no [Heroku](https://www.heroku.com).

Crie o arquivo `config/database.ci.yml` e cole nele o conteúdo abaixo:

{% gist https://gist.github.com/peimelo/7869e7a1bdc05554f72b798840afe85a file=database.ci.yml %}

Agora crie as pastas e arquivo `.github/workflows/ruby.yml` e cole nele o conteúdo abaixo:

{% gist https://gist.github.com/peimelo/7869e7a1bdc05554f72b798840afe85a file=ruby.yml %}

Verifique e altere, caso necessário, o nome do seu branch no GitHub e a versão do seu Ruby no arquivo `.github/workflows/ruby.yml`:

```ruby
# ... código existente

on:
  push:
    branches: [main] # <= AQUI
  pull_request:
    branches: [main] # <= AQUI

# ... código existente

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.0.0 # <= AQUI
          bundler-cache: true

# ... código existente
```

De agora em diante, ao fazer um push para o GitHub, seu testes irão rodar também no Actions:

![Screen Shot 2021-04-20 at 06.39.45](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2pb1sue42yh6w349p6in.png)

E se tudo ocorrer com sucesso, o deploy será realizado no Heroku, porque eu deixei marcada a opção de **Wait for CI to pass before deploy**:

![Screen Shot 2021-04-20 at 06.51.00](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mudsabmaatn8rq09g8ax.png) 

Eu gravei um vídeo sobre isso explicando em detalhes:

{% youtube XFZRNNu5z6E %}

# Conclusão

Se você chegou até aqui eu te agradeço e parabenizo, porque dessa forma você configurou seu projeto Ruby on Rails para ser seguro e confiante, porque testes são indispensáveis.

Espero que tenham gostado. Um forte abraço.