7 coisas que você precisa conhecer no RSpec
Leia em 3 minutos
O RSpec é um framework bastante completo e, por isso mesmo, muitas coisas são desconhecidas por grande parte dos desenvolvedores. Neste artigo, você conhecerá 7 coisas que irão mudar a maneira como você utiliza o RSpec.
Subject
O RSpec possui um método muito útil chamado subject
, que retorna uma instância da classe que está sendo utilizada como contexto do exemplo.
describe User do
it { should_not be_admin }
end
A grande vantagem desta abordagem é que você aumenta consideravelmente a legibilidade de suas especificações. Mas você não precisa utilizar apenas o contexto especificado no método describe
; se quiser, você pode definir o seu próprio!
describe User do
subject { User.new(:admin => true) }
it { should be_admin }
end
Quando outros tipos de objetos (como módulos ou strings) são passados ao método describe
, ele irá retornar o próprio objeto.
Message expectation
O método stub_chain
Sempre que você precisar fazer uma chamada encadeada e quiser verificar o último valor, utilize o método stub_chain
. Ele permite transformar um bloco como este
describe "Mocks" do
before do
@user = mock()
@things = mock()
@recent = mock()
@user.stub!(:things).and_return(@things)
@things.stub!(:recent).and_return(@recent)
@recent.should_receive(:count).and_return(100)
end
it "should return the recent things count from an user" do
@user.things.recent.count.should == 100
end
end
em uma coisa bem mais simples como esta
describe "Mocks" do
before do
@user = mock()
@user.stub_chain(:things, :recent, :count).and_return(100)
end
it "should return the recent things count from an user" do
@user.things.recent.count.should == 100
end
end
O método and_return
Você alguma vez precisou acessar um stub mais de uma vez e queria que ele retornasse diferentes valores em cada chamada? Veja este exemplo.
require "open-uri"
class Dice
API_URL = "http://www.random.org/integers/?num=1&min=1&max=6&col=1&base=10&format=plain&rnd=new"
def roll
open(API_URL).to_i
end
end
Toda vez que o método roll
for chamado, ele irá fazer uma requisição diferente ao http://random.org. Imagine que por algum motivo você precisasse retornar diferentes valores toda vez que o método open
você invocado. Tudo o que você precisa fazer é retornar mais de um valor com o método and_return
.
describe Dice do
before do
subject.should_receive(:open).and_return("1", "4", "2")
end
it "should not cache request" do
subject.roll.should == 1
subject.roll.should == 4
subject.roll.should == 2
end
end
O método with
O RSpec disponibiliza uma série de métodos que podem ser usados em conjunto com o método with
. Veja alguns exemplos:
module Echo
extend self
def echo(*args)
args.inspect
end
end
describe Echo do
# with any kind of argument
specify("anything") {
subject.should_receive(:echo).with(anything)
subject.echo(1)
}
# with hash containing values
specify("hash_including") {
subject.should_receive(:echo).with(hash_including(:say => "hello"))
subject.echo(:say => "hello")
}
# with an instance of String
specify("instance_of") {
subject.should_receive(:echo).with(instance_of(String))
subject.echo("hello")
}
# with an object that responds to some methods
specify("duck_type") {
subject.should_receive(:echo).twice.with(duck_type(:<<))
subject.echo(Array.new)
subject.echo(String.new)
}
end
Para ver mais exemplos, acesse https://gist.github.com/45b97f90a83c8acf3f29.
before e after globais
Muitas vezes precisamos preparar nosso ambiente antes de executarmos nossos testes. Quando isso precisa ser feito em mais de uma especificação, você pode diminuir a duplicação de código utilizando blocos globais.
No seu arquivo spec_helper.rb
, você pode utilizar os métodos append_before
, append_after
, prepend_before
e prepend_after
.
Spec::Runner.configure do |config|
config.prepend_after { `rm -rf some/path` }
end
Pending
Em vez de comentar temporariamente exemplos que você não quer que sejam executados, você pode utilizar o método pending
. O método pending
pode ser utilizado nos blocos de before
. Assim, todos os exemplos daquele contexto de describe
serão automaticamente marcados como pendente.
describe "Pending examples" do
before do
pending
end
# lots of examples
end
Você também pode deixar apenas um exemplo pendente.
describe "Pending examples" do
it "should be pending" do
pending "need to work on it"
Env.setup!
end
end
Ou apenas uma parte dele.
describe "Pending examples" do
it "should be pending with a block" do
pending("need to working on it as well") do
Env.setup!
end
end
end
Uma outra alternativa (que não é deixar como pendente mas sim desabilitado) é utilizar o método xit
em vez de it
.
describe "Pending examples" do
xit "should be pending with alias" do
Env.setup!
end
end
Matchers personalizados
Sempre que você perceber que está repetindo um padrão na hora de escrever seus exemplos, automatize o processo criando novos matchers. O RSpec possui duas maneiras diferentes de fazer isso. A mais simples delas é utilizando o método simple_matcher
. Crie um módulo chamado TheAnswerMatchers
para adicionarmos novos matchers.
module TheAnswerMatchers
def be_the_answer
simple_matcher do |given, matcher|
# save some typing
the_answer = "the Answer to Life, the Universe, and Everything"
matcher.description = "be #{the_answer}"
matcher.failure_message = "expected #{given.inspect} to be #{the_answer}"
matcher.negative_failure_message = "expected #{given.inspect} not to be #{the_answer}"
given == 42
end
end
end
Agora, basta fazer com que o RSpec reconheça esses novos matchers; é só incluir o módulo.
Spec::Runner.configure do |config|
config.include TheAnswerMatchers
end
E para escrever seus exemplos, você pode usar o matcher be_the_answer
:
describe "The Answer to Life, the Universe, and Everything" do
specify { 42.should be_the_answer }
specify { "the wrong answer".should_not be_the_answer }
end
Finalizando…
Como você pode perceber, é bem simples escrever exemplos melhores no RSpec. Se você olhar as specs do RSpec, encontrará bastante coisa legal que você normalmente não veria (porque tem muita coisa para ler) no RDocs.
E você, tem alguma dica para compartilhar?