5. Changes
● New syntax does not use “monkey patching”
● Examples are based on “expectations” over objects
6. </>
What is it about?
# ‘should’ is “replaced” by ‘expect’
#
it ‘stores 5 in its attribute’ do
expect(subject.attribute).to eq 5
end
7. </>
In one line
# One-liner syntax changes accordingly
#
it { should eq 5 }
# Is written like...
#
it { is_expected.to eq 5 }
8. Migrating
● We can migrate our current specs to the new syntax
using github.com/yujinakayama/transpec
● In 3.1.z, there’s still a mechanism to work with the old
syntax
10. Changes
● New syntax to stub methods
● Also a new syntax to set expectations on method
calls
11. </>
How to stub
# Old
instance.stub(:method)
instance.stub(:method).with(<arguments>)
instance.stub(:method).with(<arguments>).and_return(<something>)
# New
allow(instance).to receive(:method)
allow(instance).to receive(:method).with(<arguments>)
allow(instance).to receive(:method).with(<arguments>)...
12. </>
stub on any instance
# Old
Object.any_instance.stub(:method)
Object.any_instance.stub(:method).with(<arguments>)
Object.any_instance.stub(:method).with(<arguments>).and_...
# New
allow_any_instance_of(Object).to receive(:method)
allow_any_instance_of(Object).to receive(:method).with...
allow_any_instance_of(Object).to receive(:method).with...
15. </>
...on any instance
# Old
Object.any_instance.should_receive(:method)
Object.any_instance.should_receive(:method).with(<arguments>)
# New
expect_any_instance_of(Object).to receive(:method)
expect_any_instance_of(Object).to receive(:method).with...
18. </>
Many ways to skip an example
# Common way
#
it ‘stores 5 in its attribute’, skip: true do
# Giving a reason for the output
#
it ‘stores 5 in its attribute’, skip: ‘reason’ do
# Shortcut
#
skip ‘stores 5 in its attribute’ do
20. Pending should make sense
● When an example inside a pending block passes,
RSpec will show an error
● This enforces that all pending blocks are pending for
a good reason
23. </>
Chaining
# You can chain multiple ‘matchers’
#
expect(subject.attribute).
to start_with(‘hello’).and end_with(‘world’)
expect(subject.attribute).
to start_with(‘hello’).or start_with(‘goodbye’)
24. </>
The cooler way
# You can chain multiple ‘matchers’
#
expect(subject.attribute).
to start_with(‘hello’) & end_with(‘world’)
expect(subject.attribute).
to start_with(‘hello’) | start_with(‘goodbye’)
26. </>
A matcher for structures
# ‘match’ can validate Hash and Array object structures
#
let(:structure) { {key: :value, other_key: :other_value} }
expect(structure).
to match({key: :value, other_key: :other_value})
# Still works for strings and regexps as before
28. </>
Match arrays
# =~ does not work anymore as an array’s content matcher
# Now
expect(ary).to contain_exactly(1, 2, 3)
# Also in its explicit version
expect(ary).to match_array([1, 2, 3])
30. </>
Cambios en el ser
# ‘be_true’ becomes ‘be_truthy’
expect(true).to be_truthy
# ‘be_false’ becomes ‘be_falsey’ or ‘be_falsy’
expect(false).to be_falsey
expect(false).to be_falsy
# Having better semantics like in...
expect(nil).to be_falsey # instead of ‘be_false’
31. </>
Cambios en el ser
expect(nil).to be_false # Fails
expect(nil).to be_falsey # Passes
expect(‘hello world’).to be_true # Fails
expect(‘hello world’).to be_truthy # Passes
36. </>
Why, oh lord, why?
it ‘stores 5 in its attribute if attribute b is 6 and
attribute c is 7’ do
subject.attribute_b = 6
subject.attribute_c = 7
expect(subject.attribute).to eq 5
end
37. </>
Why not just...?
context ‘when attribute b is 6’ do
# ...
context ‘and attribute c is 7’ do
# ...
it ‘stores 5 in its attribute’ do
expect(subject.attribute).to eq 5
end
38. Analysis
● We can find good reasons not to do this:
○ It’s more code
○ It’s cumbersome
○ I’m just a lazy dog
● RSpec is about documentation.
● Documentation takes time.
● Good documentation takes even more time.
40. </>
This is not always a bad thing
it 'sets the attribute' do
subject.very_expensive_loading
expect(subject.attribute).to be_kind_of(Fixnum)
expect(subject.attribute).to eq(5)
end
41. </>
...but sometimes it’s unnecessary
it 'stores a fixnum in its attribute' do
expect(subject.attribute).to be_kind_of(Fixnum)
end
it 'its attribute’s fixnum is 5' do
expect(subject.attribute).to eq(5)
end
43. </>
¿What should we test for?
it 'sets the attribute to 5' do
expect(subject).to receive(:=).with(5) # Dramatization
subject.set_attribute(5)
end
# Instead of
it 'sets the attribute to 5' do
subject.set_attribute(5)
expect(subject.attribute).to eq 5
end
44. Analysis
● Not always unnecessary.
● When it’s an expensive operations like the ones that
require access to an outside service, there’s no need
to test for its results. (Assume it’s already tested)
● Don’t be scared to raise the coverage of the same
method.
46. </>
Change the subject when you can
it 'returns an array’ do
expect(subject.method).to be_kind_of(Array)
end
it 'returns an array that includes ’foo’' do
expect(subject.method).to include(:foo)
end
it 'returns an array with something else' do
# ...
47. </>
Change the subject when you can
describe '#method’s return value' do
subject { instance.method }
it 'is an array’ do
expect(subject).to be_kind_of(Array)
end
it 'includes ’foo’' do
expect(subject).to include(:foo)
end
49. </>
¡¡¡!!!
before do
@some_value = :foo
@some_other_value = :boo
@yet_some_other_value = :cmon_bro
@again_and_again = :you_are_killing_me
end
50. </>
And its little brother
it 'does something’ do
value_a = :foo
value_b = :boo
value_c = :cmon_bro
value_d = :you_are_killing_me
expect(instance.
method(value_a, value_b, value_c, value_d)).
to_not raise_error
end
51. </>
A relief in sight
let(:value_a) { :foo }
let(:value_b) { :boo }
let(:value_c) { :cmon_bro }
let(:value_d) { :you_are_killing_me }
it 'does something’ do
expect(instance.
method(value_a, value_b, value_c, value_d)).
to_not raise_error
end
56. Analysis
● create stores in the database.
● I mean, it stores in the database.
● Do we really need to persist?
● build is new without save
● The bad thing is that we have to run callbacks
manually or setting up our Factory to do so
● Is that bad, anyway?
58. </>
Look what a pretty request
# Let’s pretend ‘instance.call_api’ is a very expensive call
it 'parses the api response’ do
expect(subject.parser(instance.call_api)).
to be_kind_of(Hash)
end
59. </>
Look what a pretty request
before do
allow(instance).to receive(:call_api).
and_return(<valid response from api>)
end
it 'parses the api response’ do
expect(subject.parser(instance.call_api)).
to be_kind_of(Hash)
end
62. </>
Inherited behavior
class Foo < Bar
# …
shared_examples_for Bar do
it ‘serves drinks’
end
describe Foo do
it_behaves_like Bar
it ‘does whatever a foo does’
end
63. Analysis
● Reuse of examples (DRY).
● Consistent behavior across all subclasses.
● The idea is to only verify what’s inherited; we don’t
want to test private stuff.