Os matchers que esta suíte já usa

Prévia grátis 4 min de leitura

Este repositório tem 204 arquivos de spec: models, queries, requests, services, policies, helpers, jobs e mailers. Ler alguns mostra o que a suíte já faz bem, e uma coisa que ela não faz de jeito nenhum.

O que é um matcher

expect(user.online_time_in_hours).to eq(2.0)

O eq(2.0) é um matcher: um objeto que sabe responder este valor casa? e, mais importante, como se explicar quando a resposta é não.

Essa segunda responsabilidade é a que decide se um matcher vale a pena. Toda asserção pode ser escrita como expect(condicao).to be true, e toda escrita assim produz a mesma falha inútil: expected true, got false. Um matcher existe para dizer algo melhor.

Do spec de model deste repositório

RSpec.describe Identity::User, type: :model do
  describe 'validations' do
    subject { build(:user) }

    it { is_expected.to validate_presence_of(:email) }
    it { is_expected.to validate_uniqueness_of(:email).case_insensitive.with_message('is not available') }
  end

  describe '#update_activity!' do
    it 'updates last_activity_at to the current time' do
      user = create(:user)
      now  = Time.current
      user.update_activity!
      expect(user.reload.last_activity_at).to be_within(1.second).of(now)
    end
  end
end

Quatro coisas naquele arquivo valem nome, porque cada uma é uma escolha deliberada.

is_expected.to é expect(subject).to, que é o que torna um exemplo de uma linha legível. Com um subject definido acima, it { is_expected.to validate_presence_of(:email) } não precisa de descrição: o RSpec gera uma a partir do matcher.

validate_uniqueness_of(...).with_message('is not available') afirma a mensagem exata, que o curso de OWASP estabeleceu como uma escolha deliberada contra enumeração. Um teste que checasse só que há validação passaria se alguém revertesse a mensagem ao padrão do Rails. E a propriedade de segurança teria sumido sem nenhum spec falhando.

be_within(1.second).of(now) é o matcher certo para um timestamp. O eq(now) compararia com precisão de sub-microssegundo e falharia intermitentemente, que é a categoria de teste flaky do curso de CI chegando por uma escolha de matcher.

subject { build(:user) } usa build em vez de create, então os matchers de validação não tocam o banco até um deles precisar.

As três famílias

Família Origem Exemplo
Embutidos rspec-expectations eq, include, change, raise_error
Cientes do Rails rspec-rails have_http_status, render_template, have_enqueued_job
Cientes de model shoulda-matchers validate_presence_of, belong_to, have_many

Esta suíte configura a terceira:

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

Vale entender os shoulda-matchers como categoria: cada um exercita a validação de verdade em vez de ler a configuração da classe. O validate_uniqueness_of salva um registro e tenta salvar um conflitante. E é por isso que ele é mais lento do que parece e por que ele pega uma validação que está declarada e não funciona.

flowchart TB
  E["expect(valor).to matcher"] --> M{"casa?"}
  M -->|"sim"| P["passa, em silêncio"]
  M -->|"não"| F["failure_message: o objetivo inteiro"]
  F --> D["Uma boa mensagem nomeia a diferença"]
  F --> B["Uma mensagem ruim diz: expected true, got false"]

O que esta suíte não tem

grep -rl "RSpec::Matchers.define" spec/     # nada
grep -rl "shared_examples" spec/            # nada

Nenhum matcher próprio, e nenhum exemplo compartilhado. Com 204 arquivos de spec, 35 deles para objetos de consulta e 40 para serviços, essa é uma suíte em que o mesmo formato é afirmado repetidamente à mão.

Ela também tem uma coisa que mostra que o instinto está lá:

# spec/support/auth_helpers.rb
module AuthHelpers
  def sign_in(user)
    post auth_sign_in_path, params: { email: user.email, password: user.password }
  end
end

Um helper compartilhado, incluído para type: :request. Esse é o mesmo impulso de um exemplo compartilhado, expresso com a ferramenta que o time já conhecia. E a aula 6 é sobre quando cada uma das três ferramentas é a certa.

O que este curso cobre

  1. Os matchers que esta suíte já usa (esta aula).
  2. Lendo uma mensagem de falha.
  3. Os matchers que valem conhecer.
  4. Matchers próprios.
  5. Exemplos e contextos compartilhados.
  6. Matchers, helpers e factories.
  7. Juntando tudo: o que extrair desta suíte.

Pontos-chave

  • Um matcher responde se um valor casa e, mais importante, se explica quando não casa.
  • O is_expected.to dá exemplos de uma linha com descrições geradas; subject { build(...) } evita tocar o banco.
  • Afirmar a mensagem exata de validação é o que protege uma escolha deliberada como a redação contra enumeração.
  • be_within em vez de eq para timestamps evita um spec flaky causado por escolha de matcher.
  • Esta suíte tem 204 arquivos de spec, nenhum matcher próprio e nenhum exemplo compartilhado, que é o assunto do resto do curso.
Aula 1 de 7 · grátis

Continue com a aula 2

Lendo uma mensagem de falha

Compara quatro mensagens de falha para o mesmo erro e explica por que o matcher deve receber o próprio valor.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...