r spec let there be tests

23
Let There Be Tests Jo Cranford

Upload: jo-cranford

Post on 02-Aug-2015

281 views

Category:

Technology


3 download

TRANSCRIPT

Let There Be TestsJo Cranford

The ratio of time spent reading (code) versus writing is well over 10 to 1 … (therefore) making it easy to read makes it easier to write.

Robert C Martin

RSpec.describe Survey do

it 'closed? returns true when the status is closed' do survey = Survey.new(status: :closed) expect(survey.closed?).to be true end

end

Describe Blocks• Convention is one spec file for each Ruby file

• describe block becomes ExampleGroup subclass with metadata

• Pass RSpec a class or a description

• described_class is equal to the class definition

• Each test -> instance of ExampleGroup subclass

RSpec.describe Survey do

describe '#close?' do

it 'is true when the status is closed' do survey = Survey.new(status: :closed) expect(survey.closed?).to be true end

it 'is false when the status is closed' do survey = Survey.new(status: :open) expect(survey.closed?).to be false end

end

end

RSpec.describe Survey do

describe '#close?' do

context 'when status is closed' do

it 'is true' do survey = Survey.new(status: :closed) expect(survey.closed?).to be true end

end

context 'when status is open' do

it 'is false' do survey = Survey.new(status: :open) expect(survey.closed?).to be false end

end

end

end

RSpec.describe SurveyHelper do describe '#survey_color_indicator' do

context 'when the survey is archived' do

context 'and status is active' do

it 'is archived' do survey = Survey.new(archived: true, status: :active) expect(survey_color_indicator(survey)).to eq('archived') end

end

context 'and status is closed' do

it 'is archived' do survey = Survey.new(archived: true, status: :closed) expect(survey_color_indicator(survey)).to eq('archived') end

end

end

context 'when the survey is not archived' do

context 'and status is active' do

it 'is active' do survey = Survey.new(archived: false, status: :active) expect(survey_color_indicator(survey)).to eq('active') end

end

context 'and status is closed' do

it 'is closed' do survey = Survey.new(archived: false, status: :closed) expect(survey_color_indicator(survey)).to eq('closed') end

end

end

end

end

RSpec.describe SurveyHelper do

describe '#survey_color_indicator' do

it 'is archived when the survey is archived and status is active' do survey = Survey.new survey.archived = true survey.status = :active expect(survey_color_indicator(survey)).to eq('archived') end

it 'is archived when the survey is archived and status is closed' do survey = Survey.new survey.archived = true survey.status = :closed expect(survey_color_indicator(survey)).to eq('archived') end

it 'is active when the survey is not archived and status is active' do survey = Survey.new survey.archived = false survey.status = :active expect(survey_color_indicator(survey)).to eq('active') end

it 'is closed when the survey is not archived and status is closed' do survey = Survey.new survey.archived = false survey.status = :closed expect(survey_color_indicator(survey)).to eq('closed') end

end

end

Before and AfterRSpec.describe SurveyAdminController do describe '#index' do before do Survey.create!(name: 'My Survey') get :index end

after do Survey.destroy_all end

it 'retrieves the surveys' do expect(assigns(:surveys).size).to eq 1 end endend

AroundRSpec.describe SurveyAdminController do describe '#index' do around do |example| Survey.create!(name: 'My Survey') get :index example.run Survey.destroy_all end

it 'retrieves the surveys' do expect(assigns[:surveys].size).to eq 1 end endend

Conditional BeforeRSpec.configure do |config| config.before(:example, :authorized => true) do log_in_as :authorized_user endend

describe Something, :authorized => true do # The before hook will run in before each example in this group.end

describe SomethingElse do it "does something", :authorized => true do # The before hook will run before this example. end

it "does something else" do # The hook will not run before this example. endend

RSpec.describe SurveyHelper do describe '#survey_color_indicator' do before do @survey = Survey.new end

context 'when the survey is archived' do before do @survey.archived = true end

context 'and status is active' do before do @survey.status = :active end

it 'is archived' do expect(survey_color_indicator(@survey)).to eq('archived') end end

context 'and status is closed' do before do @survey.status = :closed end

it 'is archived' do expect(survey_color_indicator(@survey)).to eq('archived') end end end endend

Instance Variables Antipattern

• Evaluated for every single test-> slows tests down

• Spring into existence the first time that they are evaluated-> subtle bugs can creep in

• Can’t be overridden in nested blocks-> duplicated code

RSpec.describe SurveyHelper do describe '#survey_color_indicator' do before do @survey = Survey.new end

context 'when the survey is archived' do before do @survey.archived = true end

context 'and status is active' do before do @survey.status = :active end

it 'is archived' do expect(survey_color_indicator(@survey)).to eq('archived') end end

context 'and status is closed' do before do @survey.status = :closed end

it 'is archived' do expect(survey_color_indicator(@survey)).to eq('archived') end end end endend

Let vs @RSpec.describe SurveyHelper do describe '#survey_color_indicator' do let(:survey) { Survey.new(archived: is_archived, status: status) }

context 'when the survey is archived' do let(:archived) { true }

context 'and status is active' do let(:status) { :active }

it 'is archived' do expect(survey_color_indicator(survey)).to eq('archived') end end

context 'and status is closed' do let(:status) { :closed }

it 'is archived' do expect(survey_color_indicator(survey)).to eq('archived') end end end endend

Nested letsRSpec.describe SurveyAdminController do describe '#update' do let(:survey) { Survey.create!(name: 'My Survey') } let(:params) { { survey_id: survey.id } }

before do post :update, params end

context 'when no updates are specified' do it 'does not change the survey' do expect(survey.name).to eq 'My Survey' end end

context 'when the survey name is changed' do let(:params) { { survey_id: survey.id, name: 'New Name' } }

it 'updates the name' do expect(survey.name).to eq 'New Name' end end endend

let!describe SurveyAdminController do describe '#index' do let!(:survey) { Survey.create!(name: 'My Survey') }

before do get :index end

after do Survey.destroy_all end

it 'retrieves the surveys' do expect(assigns[:surveys]).to include(survey) end endend

Execution OrderRSpec.describe Something do let!(:some_variable) do # First end

before do # Second end

describe 'Nested block' do let!(:nested_variable) do # Third end

before do # Fourth end

let!(:another_one) do # Fifth end endend

Subject• Originally introduced to allow one line syntax

• Recommended by Better Specs to DRY tests

• Sometimes difficult to figure out what the subject of a test actually is

• Proceed with caution …

Implicit and Explicit Subject# Explicitdescribe Person do subject { Person.new(:birthdate => 19.years.ago) } it "should be eligible to vote" do subject.should be_eligible_to_vote # ^ ^ explicit reference to subject not recommended endend

# Implicit subject => { Person.new }.describe Person do it "should be eligible to vote" do subject.should be_eligible_to_vote # ^ ^ explicit reference to subject not recommended endend

One Line Syntax

describe SurveyResults::Employee do let(:id) { 123 } let(:email) { "[email protected]" }

subject { described_class.new(id: id, email: email) }

it { is_expected.to have_attributes(id: 123) } it { is_expected.to have_attributes(email: "[email protected]") } it { is_expected.to have_attributes(submission: nil) }end

Summary• We all spend a lot of time reading and understanding code

-> Writing clearer code and tests helps us go faster

• Nested describe and context blocks separate tests into logical blocks

• Use before to group common set up code

• let over @instance_variables to keep tests DRY, clear and performant

• let! behaves like a before, be aware of the order of execution

• Explicit subject and one line syntax make tests more succinct

• Avoid compromising readability

References• Better Specs

http://betterspecs.org/

• RSpec docshttp://www.relishapp.com/rspec/rspec-core/v/3-0/docs

• RSpec source codehttps://github.com/rspec/rspec-core

• RSpec: It’s not actually magic (RailsConf US 2015)https://www.youtube.com/watch?v=Libc0-0TRg4