Testando model em RSpec Rails.
Tags: rails, testing, rspec
Published: 2022-12-08 21:55:29

Vamos aprender a testar o model em rails usando o RSpec.

Mas antes de aprender a testar, vamos criar uma aplicação para ter uma visualização a nossa aplicação.

Eu vou criar um simples blog, em que vai ter duas tabelas.

```ruby
Post: titulo e corpo
Comentario: texto e post_id
```

Tendo a noção da minha aplicação, vamos criar ele.

## Criando a aplicação

```bash
rails new testando_model --skip-test
cd testando_model
rails db:create db:migrate
echo 'gem "rspec-rails", group: [:development, :test]' >> Gemfile
bundle install
rails g rspec:install
```

Primeiro vamos criar o model Post e migrar ele.

```bash
rails g model Post titulo corpo:text
rails db:migrate
```

Pronto com isso, nós podemos testar.

## Testando o model

Vamos abrir o arquivo `spec/models/post_spec.rb` e fazer um teste simples.

Então vamos fazer um teste simples.

```ruby
# spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  it "deve criar um post" do
    Post.create
    expect(Post.count).to equal(1)
    # traduzindo isso, seria desse jeito
    # Espero que a contagem do post seja igual 1
  end
end
```

O método `it` permite você passe o argumento de uma string que explique o seu teste e tem que utilizar o bloco para executar o seu teste.

O método `expect` recebe o argumento do valor atual, que o nosso caso é a contagem do post, e depois disso, tem o `to` que tenta comparar o valor atual de acordo com primeiro argumento, que é o método `equal` que contém o valor esperado.

Isso seria a mesma lógica que esse código que eu vou fazer, só que o meu código não foi feito para teste.

```ruby
Post.count == 1
```

Bem, vamos rodar o teste.

```bash
rspec
.

Finished in 0.08076 seconds (files took 3.83 seconds to load)
1 example, 0 failures
```

O teste passou, isso significa que pelos menos é possível criar um post.

Agora vamos nos aprofundar um pouco.

Eu quero testar que não é possível criar um post sem título e corpo.

Também quero que tenha limite de caracteres do título e do corpo.

Então eu vou mudar o primeiro teste para que possa adaptar as condições que citei em cima e adiciona mais testes.

```ruby
# spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  it "deve ser valido um post com titulo e corpo" do
    post = Post.new(
      titulo: "Instalando rpsec",
      corpo: "Lero lero"
    )
    expect(post).to be_valid
    # espero que o post seja valido
  end

  it "não deve ser valido sem o titulo" do
    post = Post.new(
      titulo: nil,
      corpo: "Lero lero"
    )
    expect(post).not_to be_valid
    # espero que o post não seja valido
  end

  it "não deve ser valido sem um titulo" do
    post = Post.new(
      titulo: "instalando rpsec",
      corpo: nil
    )
    expect(post).not_to be_valid
    # espero que o post não seja valido
  end

  it "não deve ser valido com um titulo maior que 50 caracteres" do
    post = Post.new(
      titulo: "a"*51,
      corpo: nil
    )
    expect(post).not_to be_valid
    # espero que o post não seja valido
  end

  it "não deve ser valido com um corpo maior que 500 caracteres" do
    post = Post.new(
      titulo: "Como não ser burro",
      corpo: "o"*501
    )
    expect(post).not_to be_valid
    # espero que o post não seja valido
  end
end
```

Pronto, Vamos rodar o teste.

```bash
rspec

Finished in 0.15579 seconds (files took 4.44 seconds to load)
5 examples, 4 failures

Failed examples:

rspec ./spec/models/post_spec.rb:13 # Post não deve ser valido sem o titulo
rspec ./spec/models/post_spec.rb:22 # Post não deve ser valido sem um titulo
rspec ./spec/models/post_spec.rb:31 # Post não deve ser valido com um titulo maior que 50 caracteres
rspec ./spec/models/post_spec.rb:38 # Post não deve ser valido com um corpo maior que 500 caracteres
```

É, os testes falharam, isso significa que nossa aplicação não está seguindo do jeito que nós queríamos.

Então, vamos consertar a nossa aplicação.

Primeiro, vamos para o arquivo `app/models/post.rb` e adicionar algumas validação.

```ruby
# app/models/post.rb
class Post < ApplicationRecord
  validates :titulo, presence: true, length: { maximum: 50 }
  validates :corpo, presence: true, length: { maximum: 500 } 
end
```

Agora, os testes devem passar.

```bash
rspec

.....

Finished in 0.08726 seconds (files took 4.24 seconds to load)
5 examples, 0 failures
```

Ta dã, os testes passaram, isso significa que no minimo, o seu código é minimamente confiável.

Mas vamos organizar ele, pois está feio.

```ruby
# spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  subject(:post) do
    Post.new( titulo: "Instalando rpsec", corpo: "Lero lero" )
  end

  context "é valido" do
    it "deve ser valido um post com titulo e corpo" do
      expect(post).to be_valid
    end
  end

  context "não está preenchido os campos" do
    it "não deve ser valido sem o titulo" do
      post.titulo = nil
      expect(post).not_to be_valid
    end

    it "não deve ser valido sem um titulo" do
      post.corpo = nil
      expect(post).not_to be_valid
    end
  end

  context "passou do limite de caracteres" do
    it "não deve ser valido com um titulo maior que 50 caracteres" do
      post.titulo = "a" * 51
      expect(post).not_to be_valid  end

    it "não deve ser valido com um corpo maior que 500 caracteres" do
      post.corpo = "o" * 501
      expect(post).not_to be_valid
    end
  end
end
```

Você pode notar algumas palavras novas nesse código, mas não se preocupa que eu vou explicar cada uma dela.

## Explicando o subject e context

Primeiro, vamos começar com `subject`, nós podemos dizer que é o objeto que está sendo testado, que o nosso caso seria o objeto da classe Post.

Com isso, nós podemos usá-la em qualquer parte do código.

Agora vamos falar sobre o `context`, como o nome diz, é o contexto do bloco.

No primeiro `context`, eu defino que aquele bloco, só tem validação correta.

Enquanto o segundo `context` foi definido que aquele bloco só tem validação incorreta por falta de valores.

E o terceiro, a mesma coisa que segundo, porém com ultrapassagem do limite de caracteres.

E é só isso.

Agora vamos testar o código

```bash
rspec

.....

Finished in 0.07729 seconds (files took 3.84 seconds to load)
5 examples, 0 failures
```

Está funcionando.

Vamos tentar adicionar um teste que vai falhar com certeza absoluta.

Eu quero testar se o post tem comentário e quantos ele tem.

Eu vou omitir os outros testes para ocupar menos espaços.

```ruby
# spec/models/post_spec.rb
...
  context "#tem_quantos_comentario" do
    it "deve ter dois comentario" do
      Comentario.create(texto: "Foda", post: post)
      Comentario.create(texto: "Gostei", post: post)

      expect(post.tem_quantos_comentario).to equal(2)
    end

    it "não deve ter nenhum comentario" do
      expect(post.tem_quantos_comentario).to equal(0)
    end
  end

  context "#tem_comentario?" do
    it "deve retornar true" do
      Comentario.create(texto: "Foda", post: post)

      expect(post.tem_comentario?).to be_truthy
    end

    it "deve retornar false" do
      expect(post.tem_comentario?).to be_falsey
    end
  end
```

Vcoê pode ter percebido esse `#` antes do nome no context.

Isso é um forma de dizer que esse contexto está testando o método da instância. Se você colocar um `.` antes do método, você está dizendo que o contexto é testar o método da classe.

Se nós rodar o código, obviamente vai falhar, porque ainda não existe o model Comentario e os métodos, então vamos criar eles.

```bash
rails g model Comentario texto post:references
rails db:migrate
```

Agora vamos criar os métodos e colocar associação do comentário com post.

```ruby
# app/models/post.rb

class Post < ApplicationRecord
  has_many :comentarios

  validates :titulo, presence: true, length: { maximum: 50 }
  validates :corpo, presence: true, length: { maximum: 500 } 

  def tem_quantos_comentario
    self.comentarios.count
  end

  def tem_comentario?
    self.comentarios.count >= 1
  end
end
```

Agora vamos testar o código.

```bash
rspec

*.........

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Comentario add some examples to (or delete) /tmp/testando_model/spec/models/comentario_spec.rb
     # Not yet implemented
     # ./spec/models/comentario_spec.rb:4


Finished in 0.19358 seconds (files took 3.74 seconds to load)
10 examples, 0 failures, 1 pending
```

Está tudo funcionando.

Bem, eu não vou testar o comentário, porque não seria muito diferente do teste Post.

Então eu deixo você fazer o resto.

Pronto, com isso, nós podemos dar a segurança aos nossos models com esses testes.

É claro que os nossos testes feito aqui não o suficiente.

Nós temos que testar a associação, remoção de dados de banco de dados etc.

No próximo post, eu vou testar os requests.

E é isso, Tchau!