2. 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
3. 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
4. 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
5. 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
6. 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
7. 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
8. Before and After
RSpec.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
end
end
9. Around
RSpec.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
end
end
10. Conditional Before
RSpec.configure do |config|
config.before(:example, :authorized => true) do
log_in_as :authorized_user
end
end
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.
end
end
11. 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
end
end
12. 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
13. 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
end
end
14. 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
end
end
15. Nested lets
RSpec.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
end
end
16. 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
end
end
17. Execution Order
RSpec.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
end
end
18. 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 …
19. Implicit and Explicit Subject
# Explicit
describe 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
end
end
# 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
end
end
20. One Line Syntax
describe SurveyResults::Employee do
let(:id) { 123 }
let(:email) { "man@moon.com" }
subject { described_class.new(id: id, email: email) }
it { is_expected.to have_attributes(id: 123) }
it { is_expected.to have_attributes(email: "man@moon.com") }
it { is_expected.to have_attributes(submission: nil) }
end
21. 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