Os matchers que esta suíte já usa
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
- Os matchers que esta suíte já usa (esta aula).
- Lendo uma mensagem de falha.
- Os matchers que valem conhecer.
- Matchers próprios.
- Exemplos e contextos compartilhados.
- Matchers, helpers e factories.
- 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.todá 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_withinem vez deeqpara 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.
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.