RSpec 3: The new, the old, the good

70
RSpec 3

description

A presentation used at one of Moove-IT's Friday Talks to introduce the essential changes from RSpec 2 to RSpec 3.

Transcript of RSpec 3: The new, the old, the good

Page 1: RSpec 3: The new, the old, the good

RSpec 3

Page 2: RSpec 3: The new, the old, the good

Contents

1. Changes from v2.14 to v3.y.z2. Better Specs reviewed

Page 3: RSpec 3: The new, the old, the good

Changes fromv2.14 to v3.y.z

Page 4: RSpec 3: The new, the old, the good

Expect to not‘should’ anymore

Page 5: RSpec 3: The new, the old, the good

Changes

● New syntax does not use “monkey patching”● Examples are based on “expectations” over objects

Page 6: RSpec 3: The new, the old, the good

</>

What is it about?# ‘should’ is “replaced” by ‘expect’

#

it ‘stores 5 in its attribute’ do

expect(subject.attribute).to eq 5

end

Page 7: RSpec 3: The new, the old, the good

</>

In one line# One-liner syntax changes accordingly

#

it { should eq 5 }

# Is written like...

#

it { is_expected.to eq 5 }

Page 8: RSpec 3: The new, the old, the good

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

Page 9: RSpec 3: The new, the old, the good

Don’t stub. Allow.

Page 10: RSpec 3: The new, the old, the good

Changes

● New syntax to stub methods● Also a new syntax to set expectations on method

calls

Page 11: RSpec 3: The new, the old, the good

</>

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>)...

Page 12: RSpec 3: The new, the old, the good

</>

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...

Page 13: RSpec 3: The new, the old, the good

</>

stub chain# Old

instance.stub_chain(:method_a, :method_b)

# New

allow(instance).to receive_message_chain(:method_a, :method_b)

Page 14: RSpec 3: The new, the old, the good

</>

Method expectations# Old

instance.should_receive(:method)

instance.should_receive(:method).with(<arguments>)

# New

expect(instance).to receive(:method)

expect(instance).to receive(:method).with(<arguments>)

Page 15: RSpec 3: The new, the old, the good

</>

...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...

Page 16: RSpec 3: The new, the old, the good

Tools of trade

Page 17: RSpec 3: The new, the old, the good

Skip it...

Page 18: RSpec 3: The new, the old, the good

</>

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

Page 19: RSpec 3: The new, the old, the good

...or leave it pending

Page 20: RSpec 3: The new, the old, the good

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

Page 21: RSpec 3: The new, the old, the good

Matchmakers

Page 22: RSpec 3: The new, the old, the good

Cha-cha-chain

Page 23: RSpec 3: The new, the old, the good

</>

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’)

Page 24: RSpec 3: The new, the old, the good

</>

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’)

Page 25: RSpec 3: The new, the old, the good

Structural matching

Page 26: RSpec 3: The new, the old, the good

</>

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

Page 27: RSpec 3: The new, the old, the good

=~?

Page 28: RSpec 3: The new, the old, the good

</>

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])

Page 29: RSpec 3: The new, the old, the good

be_yourself

Page 30: RSpec 3: The new, the old, the good

</>

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’

Page 31: RSpec 3: The new, the old, the good

</>

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

Page 32: RSpec 3: The new, the old, the good

What’s goneis kind of gone

Page 33: RSpec 3: The new, the old, the good

The fired matchers

● its

github.com/rspec/rspec-its● have, have_at_least, have_at_most

github.com/rspec/rspec-collection_matchers

Page 34: RSpec 3: The new, the old, the good

Better Specs reviewed

Page 35: RSpec 3: The new, the old, the good

Put some context

Page 36: RSpec 3: The new, the old, the good

</>

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

Page 37: RSpec 3: The new, the old, the good

</>

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

Page 38: RSpec 3: The new, the old, the good

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.

Page 39: RSpec 3: The new, the old, the good

IsolationVS

Combination

Page 40: RSpec 3: The new, the old, the good

</>

This is not always a bad thingit 'sets the attribute' do

subject.very_expensive_loading

expect(subject.attribute).to be_kind_of(Fixnum)

expect(subject.attribute).to eq(5)

end

Page 41: RSpec 3: The new, the old, the good

</>

...but sometimes it’s unnecessaryit '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

Page 42: RSpec 3: The new, the old, the good

The right methodVS

The right result

Page 43: RSpec 3: The new, the old, the good

</>

¿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

Page 44: RSpec 3: The new, the old, the good

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.

Page 45: RSpec 3: The new, the old, the good

Change the subject

Page 46: RSpec 3: The new, the old, the good

</>

Change the subject when you canit '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

# ...

Page 47: RSpec 3: The new, the old, the good

</>

Change the subject when you candescribe '#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

Page 48: RSpec 3: The new, the old, the good

Let it be

Page 49: RSpec 3: The new, the old, the good

</>

¡¡¡!!!before do

@some_value = :foo

@some_other_value = :boo

@yet_some_other_value = :cmon_bro

@again_and_again = :you_are_killing_me

end

Page 50: RSpec 3: The new, the old, the good

</>

And its little brotherit '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

Page 51: RSpec 3: The new, the old, the good

</>

A relief in sightlet(: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

Page 52: RSpec 3: The new, the old, the good

Analysis

● Reuse of variables (DRY)● Lazy loading● Readability (Good documentation)

Page 53: RSpec 3: The new, the old, the good

Avoid creatinga monster

Page 54: RSpec 3: The new, the old, the good

</>

Don’t get excited about createlet(:model) { create(:model) }

# ORLY?

Page 55: RSpec 3: The new, the old, the good

</>

Don’t get excited about createlet(:model) { build(:model) }

Page 56: RSpec 3: The new, the old, the good

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?

Page 57: RSpec 3: The new, the old, the good

Stub when you have to

Page 58: RSpec 3: The new, the old, the good

</>

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

Page 59: RSpec 3: The new, the old, the good

</>

Look what a pretty requestbefore 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

Page 60: RSpec 3: The new, the old, the good

Learn to share

Page 61: RSpec 3: The new, the old, the good

</>

Inherited behaviorclass Foo < Bar

# …

describe Foo do

it ‘serves drinks’

it ‘does whatever a foo does’

Page 62: RSpec 3: The new, the old, the good

</>

Inherited behaviorclass 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

Page 63: RSpec 3: The new, the old, the good

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.

Page 64: RSpec 3: The new, the old, the good

Agree to disagree

Page 65: RSpec 3: The new, the old, the good

David Heinemeier Hansson, creator of Ruby on Rails

Page 66: RSpec 3: The new, the old, the good

David Heinemeier Hansson, creator of Ruby on Rails

Page 67: RSpec 3: The new, the old, the good

David Heinemeier Hansson, creator of Ruby on Rails

Page 68: RSpec 3: The new, the old, the good

David Heinemeier Hansson, creator of Ruby on Rails

Page 69: RSpec 3: The new, the old, the good

myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3betterspecs.orgrubyinside.com/dhh-offended-by-rspec-debate-4610.html

Sources

Page 70: RSpec 3: The new, the old, the good

La fin