Everydayrailsrspec Sample PDF
Everydayrailsrspec Sample PDF
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.
2012 - 2014 Aaron Sumner
Contents
Preface to this edition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1. Introduction . . . . . . . . . .
Why RSpec? . . . . . . . . . .
Who should read this book . .
My testing philosophy . . . .
How the book is organized . .
Downloading the sample code
Code conventions . . . . . . .
Discussion and errata . . . . .
About the sample application .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
2
4
5
6
8
9
9
3. Model specs . . . . . . . . . . . . . . . . . . . . . .
Anatomy of a model spec . . . . . . . . . . . . . . .
Creating a model spec . . . . . . . . . . . . . . . . .
The new RSpec syntax . . . . . . . . . . . . . . . .
Testing validations . . . . . . . . . . . . . . . . . .
Testing instance methods . . . . . . . . . . . . . . .
Testing class methods and scopes . . . . . . . . . . .
Testing for failures . . . . . . . . . . . . . . . . . .
More about matchers . . . . . . . . . . . . . . . . .
DRYer specs with describe, context, before and after
Summary . . . . . . . . . . . . . . . . . . . . . . .
Question . . . . . . . . . . . . . . . . . . . . . . . .
Exercises . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
13
15
17
21
22
24
25
25
32
33
33
34
CONTENTS
35
Colophon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
https://github.com/everydayrails/rails-4-1-rspec-3-0/issues
1. Introduction
Ruby on Rails and automated testing go hand in hand. Rails ships with a built-in test
framework; if its not to your liking you can replace it with one of your liking. As I
write this, Ruby Toolbox lists 17 projects under the Unit Test Frameworks category
alone. So, yeah, testings pretty important in Rails. Yet, many people developing in
Rails are either not testing their projects at all, or at best only adding a few token
specs on model validations.
In my opinion, there are several reasons for this. Perhaps working with Ruby or web
frameworks is a novel enough concept, and adding an extra layer of work seems
like just thatextra work. Or maybe there is a perceived time constraintspending
time on writing tests takes time away from writing the features our clients or bosses
demand. Or maybe the habit of defining test as the practice of clicking links in the
browser is just too hard to break.
Ive been there. Historically, I havent considered myself an engineer in the traditional senseyet just like traditional engineers, I have problems to solve. And,
typically, I find solutions to these problems in building software. Ive been developing
web applications since 1995, but usually as a solo developer on shoestring, public
sector projects. Aside from some structured exposure to BASIC as a kid, a little C++
in college, and a wasted week of Java training in my second grown-up job outside of
college, Ive never had any honest-to-goodness schooling in software development.
In fact, it wasnt until 2005, when Id had enough of hacking ugly spaghetti-style
PHP code, that I sought out a better way to write web applications.
Id looked at Ruby before, but never had a serious use for it until Rails began gaining
steam. There was a lot to learna new language, an actual architecture, and a more
object-oriented approach (despite what you may think about Rails treatment of
object orientation, its far more object oriented than anything I wrote in my preframework days). Even with all those new challenges, though, I was able to create
https://www.ruby-toolbox.com/categories/testing_frameworks
http://en.wikipedia.org/wiki/Spaghetti_code
1. Introduction
complex applications in a fraction of the time it took me in my previous frameworkless efforts. I was hooked.
That said, early Rails books and tutorials focused more on speed (build a blog in 15
minutes!) than on good practices like testing. If testing were covered at all, it was
generally reserved for a chapter toward the end. Now, to be fair, newer educational
resources on Rails have addressed this shortcoming, and now demonstrate how to test
applications from the beginning. In addition, a number of books have been written
specifically on the topic of testing. But without a sound approach to the testing side,
many developersespecially those in a similar boat to the one I was inmay find
themselves without a consistent testing strategy.
My goal with this book is to introduce you to a consistent strategy that works for
meone that you can then, hopefully, adapt to make work consistently for you, too.
Why RSpec?
For the most part, I have nothing against the other test frameworks out there. If Im
writing a standalone Ruby library, I usually rely on MiniTest. For whatever reason,
though, RSpec is the one thats stuck with me when it comes to testing my Rails
applications.
Maybe it stems from my backgrounds in copywriting and software development,
but for me RSpecs capacity for specs that are readable, without being cumbersome,
is a winner. Ill talk more about this later in the book, but Ive found that with a
little coaching even most non-technical people can read a spec written in RSpec and
understand whats going on.
1. Introduction
Ruby on Rails, or Sam Rubys Agile Web Development with Rails 4, before digging
into Everyday Rails Testing with RSpecthis book assumes youve got some basic
Rails skills under your belt. In other words, this book wont teach you how to use
Rails, and it wont provide a ground-up introduction to the testing tools built into the
frameworkwere going to be installing RSpec and a few extras to make the testing
process as easy as possible to comprehend and manage.
If youve been developing in Rails for a little while, and maybe even have an
application or two in productionbut testing is still a foreign conceptthis book is for
you! I was in your shoes for a long time, and the techniques Ill share here helped me
improve my test coverage and think more like a test-driven developer. I hope theyll
do the same for you.
Specifically, you should probably have a grasp of
On the more advanced end, if youre familiar with using Test::Unit, MiniTest, or
even RSpec itself, and already have a workflow in place that (a) youre comfortable
with and (b) provides adequate coverage, you may be able to fine-tune some of your
approach to testing your applications. But to be honest, at this point youre probably
on board with automated testing and dont need this extra nudge. This is not a book
on testing theory; it also wont dig too deeply into performance issues. Other books
may be of more use to you in the long run.
Refer to More Testing Resources for Rails at the end of this book for links
to these and other books, websites, and testing tutorials.
1. Introduction
My testing philosophy
Discussing the right way to test your Rails application can invoke major shouting
matches amongst programmersnot quite as bad as, say, the Vim versus Emacs
debate, but still not something to bring up in an otherwise pleasant conversation
with fellow Rubyists. In fact, David Heinemeier-Hansens keynote at Railsconf 2014,
in which he declared TDD as dead, has sparked a fresh round of debates on the
topic.
So, yes, there is a right way to do testingbut if you ask me, there are degrees of right
when it comes to testing.
At the risk of starting additional riots among the Ruby test-driven/behavior-driven
development communities, my approach focuses on the following foundation:
Tests should be reliable.
Tests should be easy to write.
Tests should be easy to understand.
If you mind these three factors in your approach, youll go a long way toward
having a sound test suite for your applicationnot to mention becoming an honestto-goodness practitioner of Test-Driven Development. Whatever that means these
days.
Yes, there are some tradeoffsin particular:
Were not focusing on speed (though we will talk about it later).
Were not focusing on overly DRY code in our tests, but in tests, thats not
necessarily a bad thing. Well talk about this, too.
In the end, though, the most important thing is that youll have testsand reliable,
understandable tests, even if theyre not quite as optimized as they could be, are
a great way to start. Its the approach that finally got me over the hump between
writing a lot of application code, calling a round of browser-clicking testing, and
hoping for the best; versus taking advantage of a fully automated test suite and using
tests to drive development and ferret out potential bugs and edge cases.
And thats the approach well take in this book.
1. Introduction
1. Introduction
a few exercises to follow when using these techniques on your own. Again, I strongly
recommend working through the exercises in your own applicationsits one thing
to follow along with a tutorial; its another thing entirely to apply what you learn to
your own situation. We wont be building an application together in this book, just
exploring code patterns and techniques. Take those techniques and make your own
projects better!
If youre familiar with Git (and, as a Rails developer, you should be), you can clone
the source to your computer. Each chapters work has its own branch. Grab that
chapters source to see the completed code, or the previous chapters source if youd
like to follow along with the book. Branches are labeled by chapter number, but Ill
also tell you which branch to check out at the start of that chapter.
If youre not familiar with Git, you may still download the sample code a given
chapter. To begin, open the project on GitHub. Then, locate the branch selector and
select that chapters branch:
1. Introduction
Finally, click the ZIP download button to save the source to your computer:
1. Introduction
Code conventions
Im using the following setup for this application:
Rails 4.1: The latest version of Rails is the big focus of this book; however, as
far as I know the techniques Im using will apply to any version of Rails from
3.0 onward. Your mileage may vary with some of the code samples, but Ill do
my best to let you know where things might differ.
Ruby 2.1: I dont think youll see any major differences if youre using 1.9 or
2.0. At this point I dont recommend trying to progress through the book if
youre still using Ruby 1.8.
http://gitimmersion.com/
http://try.github.io
http://gitref.org
1. Introduction
RSpec 3.1: RSpec 3.0 was released in spring, 2014. RSpec 3.1 appeared a few
months later and is by and large compatible with the 3.0 release. Its relatively
close in syntax to RSpec 2.14, though there are a few differences.
If somethings particular to these versions, Ill do my best to point it out. If youre
working from an older version of any of the above, previous versions of the book
are available as free downloads through Leanpub with your paid purchase of this
edition. Theyre not feature-for-feature identical, but you should hopefully be able
to see some of the basic differences.
Again, this book is not a traditional tutorial! The code provided here isnt intended
to walk you through building an application; rather, its here to help you understand
and learn testing patterns and habits to apply to your own Rails applications. In other
words, you can copy and paste, but its probably not going to do you a lot of good.
You may be familiar with this technique from Zed Shaws Learn Code the Hard Way
seriesEveryday Rails Testing with RSpec is not in that exact style, but I do agree
with Zed that typing things yourself as opposed to copying-and-pasting from the
interwebs or an ebook is a better way to learn.
1. Introduction
10
changes to existing ones. Finally, users must have an administrator ability to add
new users to the system.
Up to this point, though, Ive been intentionally lazy and only used Rails default
generators to create the entire application (see the 01_untested branch of the sample
code). This means I have a test directory full of untouched test files and fixtures. I
could run rake test at this point, and perhaps some of these tests would even pass.
But since this is a book about RSpec, a better solution will be to dump this folder,
set up Rails to use RSpec instead, and build out a more respectable test suite. Thats
what well walk through in this book.
First things first: We need to configure the application to recognize and use RSpec
and to start generating the appropriate specs (and a few other useful files) whenever
we employ a Rails generator to add code to the application.
Lets get started!
3. Model specs
Weve got all the tools we need for building a solid, reliable test suitenow its time
to put them to work. Well get started with the apps core building blocksits models.
In this chapter, well complete the following tasks:
First well create a model spec for an existing modelin our case, the actual
Contact model.
Then, well write passing tests for a models validations, class, and instance
methods, and organize our spec in the process.
Well create our first spec files for existing models by hand. If and when we add
new models to the application (OK, when we do in chapter 11), the handy RSpec
generators we configured in chapter 2 will generate placeholder files for us.
Check out the 03_models branch of the sample source to see the completed
code for this chapter. Using the command line, type
git checkout -b 03_models origin/03_models
If youd like to follow along, start with the previous chapters branch:
git checkout -b 02_setup origin/02_setup
3. Model specs
12
The models create method, when passed valid attributes, should be valid.
Data that fail validations should not be valid.
Class and instance methods perform as expected.
This is a good time to look at the basic structure of an RSpec model spec. I find it
helpful to think of them as individual outlines. For example, lets look at our main
Contact models requirements:
describe Contact do
it "is valid with a firstname, lastname and email"
it "is invalid without a firstname"
it "is invalid without a lastname"
it "is invalid without an email address"
it "is invalid with a duplicate email address"
it "returns a contact's full name as a string"
end
Well expand this outline in a few minutes, but this gives us quite a bit for starters.
Its a simple spec for an admittedly simple model, but points to our first four best
practices:
It describes a set of expectationsin this case, what the Contact model should
look like, and how it should behave.
Each example (a line beginning with it) only expects one thing. Notice
that Im testing the firstname, lastname, and email validations separately.
This way, if an example fails, I know its because of that specific validation,
and dont have to dig through RSpecs output for cluesat least, not as deeply.
Each example is explicit. The descriptive string after it is technically optional
in RSpec. However, omitting it makes your specs more difficult to read.
Each examples description begins with a verb, not should. Read the expectations aloud: Contact is invalid without a firstname, Contact is invalid without
a lastname, Contact returns a contacts full name as a string. Readability is
important!
With these best practices in mind, lets build a spec for the Contact model.
13
3. Model specs
require 'rails_helper'
2
3
4
5
6
7
8
9
10
describe Contact do
it "is valid with a firstname, lastname and email"
it "is invalid without a firstname"
it "is invalid without a lastname"
it "is invalid without an email address"
it "is invalid with a duplicate email address"
it "returns a contact's full name as a string"
end
Notice the require 'rails_helper' at the top, and get used to typing itall of
your specs will include this line moving forward. This is a new addition to RSpec
3previous version required spec_helper, but the Rails-specific details have been
extracted to make the main helper significantly lighter. Well touch on this a little
further in chapter 9.
Well fill in the details in a moment, but if we ran the specs right now from the
command line (by typing bin/rspec or just rspec on the command line) the output
would be similar to the following:
3. Model specs
Contact
is valid with a firstname, lastname and email
(PENDING: Not yet implemented)
is invalid without a firstname
(PENDING: Not yet implemented)
is invalid without a lastname
(PENDING: Not yet implemented)
is invalid without an email address
(PENDING: Not yet implemented)
is invalid with a duplicate email address
(PENDING: Not yet implemented)
returns a contact's full name as a string
(PENDING: Not yet implemented)
Pending:
Contact is valid with a firstname, lastname
and email
# Not yet implemented
# ./spec/models/contact_spec.rb:4
Contact is invalid without a firstname
# Not yet implemented
# ./spec/models/contact_spec.rb:5
Contact is invalid without a lastname
# Not yet implemented
# ./spec/models/contact_spec.rb:6
Contact is invalid without an email address
# Not yet implemented
# ./spec/models/contact_spec.rb:7
Contact is invalid with a duplicate email address
# Not yet implemented
# ./spec/models/contact_spec.rb:8
Contact returns a contact's full name as a string
# Not yet implemented
# ./spec/models/contact_spec.rb:9
Finished in 0.00105 seconds (files took 2.42 seconds to load)
6 examples, 0 failures, 6 pending
14
15
3. Model specs
Great! Six pending specslets write them and make them pass, starting with the first
example.
As we add additional models to the contacts manager, assuming we use
Rails model or scaffold generator to do so, the model spec file will be
added automatically. If it doesnt go back and configure your applications
generators now, or make sure youve properly installed the rspec-rails
gem, as shown in chapter 2. Youll still need to fill in the details, though.
The new syntax passes the test value into an expect() method, then chains a matcher
to it:
http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
3. Model specs
16
If youre searching Google or Stack Overflow for help with an RSpec question, theres
still a good chance youll find information using the old should syntax. This syntax
still technically works in RSpec 3, but youll get a deprecation warning when you
try to use it. You can configure RSpec to turn off these warnings, but in all honesty,
youre better off learning to use the preferred expect() syntax.
So what does that syntax look like in a real example? Lets fill out that first
expectation from our spec for the Contact model:
spec/models/contact_spec.rb
1
require 'rails_helper'
2
3
4
5
6
7
8
9
10
describe Contact do
it "is valid with a firstname, lastname and email" do
contact = Contact.new(
firstname: 'Aaron',
lastname: 'Sumner',
email: '[email protected]')
expect(contact).to be_valid
end
11
12
13
This simple example uses RSpecs be_valid matcher to verify that our model knows
what it has to look like to be valid. We set up an object (in this case, a new-butunsaved instance of Contact called contact), then pass that to expect to compare
to the matcher.
Now, if we run RSpec from the command line again (via bin/rspec or bundle exec
rspec, depending on whether you installed the rspec binstub in the previous chapter)
we see one passing example! Were on our way. Now lets get into testing more of
our code.
3. Model specs
17
Testing validations
Validations are a good way to break into automated testing. These tests can usually
be written in just a line or two of code, especially when we leverage the convenience
of factories (next chapter). Lets look at some detail to our firstname validation spec:
spec/models/contact_spec.rb
1
2
3
4
5
This time, we expect that the new contact (with a firstname explicitly set to nil)
will not be valid, thus returning the shown error message on the contacts firstname
attribute. We check for this using RSpecs include matcher, which checks to see if a
value is included in an enumerable value. And when we run RSpec again, we should
be up to two passing specs.
To prove that were not getting false positives, lets flip that expectation by changing
to to not_to:
spec/models/contact_spec.rb
1
2
3
4
5
18
3. Model specs
Failures:
1) Contact is invalid without a firstname
Failure/Error: expect(contact.errors[:firstname]).not_to
include("can't be blank")
expected ["can't be blank"] not to include "can't be blank"
# ./spec/models/contact_spec.rb:15:in `block (2 levels) in
<top (required)>'
RSpec provides not_to and to_not for these types of expectations. Theyre
interchangeable. I use not_to in the book.
This is an easy way to verify your tests are working correctly, especially as you
progress from testing simple validations to more complex logic. Just remember to
flip that not_to back to to before continuing.
If youve used an earlier version of RSpec, you may be used to using
the have matcher and errors_on helper method to check for validation
errors. These have been removed from RSpec 3s core. You can still
use the have matcher by including rspec-collection_matchers in your
Gemfiles :test group.
Now we can use the same approach to test the :lastname validation.
spec/models/contact_spec.rb
1
2
3
4
5
You may be thinking that these tests are relatively pointlesshow hard is it to make
sure validations are included in a model? The truth is, they can be easier to omit than
3. Model specs
19
you might imagine. More importantly, though, if you think about what validations
your model should have while writing tests (ideally, and eventually, in a Test-Driven
Development style of coding), you are more likely to remember to include them.
Testing that email addresses must be unique is fairly simple as well:
spec/models/contact_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
Notice a subtle difference here: In this case, we persisted a contact (calling create
on Contact instead of new) to test against, then instantiated a second contact as the
subject of the actual test. This, of course, requires that the first, persisted contact is
valid (with both a first and last name) and has an email address assigned to it. In
future chapters well look at utilities to streamline this process.
Now lets test a more complex validation. Say we want to make sure we dont
duplicate a phone number for a usertheir home, office, and mobile phones should
all be unique within the scope of that user. How might you test that?
Switching to the Phone model spec, we have the following example:
3. Model specs
spec/models/phone_spec.rb
1
require 'rails_helper'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
describe Phone do
it "does not allow duplicate phone numbers per contact" do
contact = Contact.create(
firstname: 'Joe',
lastname: 'Tester',
email: '[email protected]'
)
contact.phones.create(
phone_type: 'home',
phone: '785-555-1234'
)
mobile_phone = contact.phones.build(
phone_type: 'mobile',
phone: '785-555-1234'
)
18
19
20
21
mobile_phone.valid?
expect(mobile_phone.errors[:phone]).to include('has already been taken')
end
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
20
3. Model specs
37
21
38
39
40
41
expect(other_phone).to be_valid
end
end
This time, since the Contact and Phone models are coupled via an Active Record
relationship, we need to provide a little extra information. In the case of the first
example, weve got a contact to which both phones are assigned. In the second, the
same phone number is assigned to two unique contacts. Note that, in both examples,
we have to create the contact, or persist it in the database, in order to assign it to the
phones were testing.
And since the Phone model has the following validation:
app/models/phone.rb
validates :phone, uniqueness: { scope: :contact_id }
22
3. Model specs
app/models/contact.rb
1
2
3
def name
[firstname, lastname].join(' ')
end
We can use the same basic techniques we used for our validation examples to create
a passing example of this feature:
spec/models/contact_spec.rb
1
2
3
4
5
Create test data, then tell RSpec how you expect it to behave. Easy, right? Lets keep
going.
3. Model specs
23
app/models/contact.rb
1
2
3
def self.by_letter(letter)
where("lastname LIKE ?", "#{letter}%").order(:lastname)
end
require 'rails_helper'
2
3
describe Contact do
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Note were testing both the results of the query and the sort order; jones will be
retrieved from the database first but since were sorting by last name then johnson
should be stored first in the query results.
3. Model specs
24
require 'rails_helper'
2
3
describe Contact do
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
This spec uses RSpecs include matcher to determine if the array returned by
Contact.by_letter("J")and it passes! Were testing not just for ideal resultsthe
user selects a letter with resultsbut also for letters with no results.
3. Model specs
25
https://github.com/rspec/rspec-expectations
3. Model specs
26
spec/models/contact_spec.rb
1
require 'rails_helper'
2
3
describe Contact do
4
5
6
7
8
9
10
Lets break things down even further by including a couple of context blocksone
for matching letters, one for non-matching:
spec/models/contact_spec.rb
1
require 'rails_helper'
2
3
describe Contact do
4
5
6
7
8
9
10
11
12
13
14
15
16
27
3. Model specs
As you may be able to spot, were creating an outline of examples here to help us
sort similar examples together. This makes for a more readable spec. Now lets finish
cleaning up our reorganized spec with the help of a before hook:
spec/models/contact_spec.rb
1
require 'rails_helper'
2
3
describe Contact do
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
28
3. Model specs
27
28
29
30
31
32
33
34
RSpecs before hooks are vital to cleaning up nasty redundancy from your specs.
As you might guess, the code contained within the before block is run before
each example within the describe blockbut not outside of that block. Since weve
indicated that the hook should be run before each example within the block, RSpec
will create them for each example individually. In this example, my before hook
will only be called within the describe "filter last name by letter" blockin
other words, my original validation specs will not have access to @smith, @jones,
and @johnson.
:each is the default behavior of before, and many Rubyists use the shorter
before do to create before blocks. I prefer the explicitness of before
:each do and will use it throughout the book.
Speaking of my three test contacts, note that since they are no longer being created
within each example, we have to assign them to instance variables, so theyre
accessible outside of the before block, within our actual examples.
If a spec requires some sort of post-example teardowndisconnecting from an
external service, saywe can also use an after hook to clean up after the examples.
Since RSpec handles cleaning up the database by default, I rarely use after. before,
though, is indispensable.
Okay, lets see that full, organized spec:
3. Model specs
spec/models/contact_spec.rb
1
require 'rails_helper'
2
3
4
5
6
7
8
9
10
describe Contact do
it "is valid with a firstname, lastname and email" do
contact = Contact.new(
firstname: 'Aaron',
lastname: 'Sumner',
email: '[email protected]')
expect(contact).to be_valid
end
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
29
3. Model specs
37
38
39
40
41
email: '[email protected]'
)
contact.valid?
expect(contact.errors[:email]).to include("has already been taken")
end
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
30
31
3. Model specs
75
end
76
77
78
79
80
81
82
83
When we run the specs well see a nice outline (since we told RSpec to use the
documentation format, in chapter 2) like this:
Contact
is valid with a firstname, lastname and email
is invalid without a firstname
is invalid without a lastname
is invalid without an email address
is invalid with a duplicate email address
returns a contact's full name as a string
filter last name by letter
with matching letters
returns a sorted array of results that match
with non-matching letters
omits results that do not match
Phone
does not allow duplicate phone numbers per contact
allows two contacts to share a phone number
Finished in 0.51654 seconds (files took 2.24 seconds to load)
10 examples, 0 failures
Some developers prefer to use method names for the descriptions of nested
describe blocks. For example, I could have labeled filter last name
by letter as #by_letter. I dont like doing this personally, as I believe
the label should define the behavior of the code and not the name of the
method. That said, I dont have a strong opinion about it.
3. Model specs
32
Summary
This chapter focused on how I test models, but weve covered a lot of other important
techniques youll want to use in other types of specs moving forward:
Use active, explicit expectations: Use verbs to explain what an examples
results should be. Only check for one result per example.
Test for what you expect to happen, and for what you expect to not
happen: Think about both paths when writing examples, and test accordingly.
Test for edge cases: If you have a validation that requires a password be
between four and ten characters in length, dont just test an eight-character
password and call it good. A good set of tests would test at four and ten, as
well as at three and eleven. (Of course, you might also take the opportunity to
ask yourself why youd allow such short passwords, or not allow longer ones.
Testing is also a good opportunity to reflect on an applications requirements
and code.)
3. Model specs
33
Organize your specs for good readability: Use describe and context to
sort similar examples into an outline format, and before and after blocks
to remove duplication. However, in the case of tests readability trumps DRY
if you find yourself having to scroll up and down your spec too much, its okay
to repeat yourself a bit.
With a solid collection of model specs incorporated into your app, youre well on
your way to more trustworthy code. In the next chapter well apply and expand
upon the techniques covered here to application controllers.
Question
When should I use describe versus context? From RSpecs perspective, you can
use describe all the time, if youd like. Like many other aspects of RSpec, context
exists to make your specs more readable. You could take advantage of this to match
a condition, as Ive done in this chapter, or some other state in your application.
Exercises
So far weve assumed our specs arent returning false positivestheyve all gone from
pending to passing without failing somewhere in the middle. Verify specs by doing
the following:
Comment out the application code youre testing. For example, in our
example that validates the presence of a contacts first name, we could
comment out validates :firstname, presence: true, run the specs, and
watch it "is invalid without a firstname" fail. Uncomment it to see the
spec pass again.
Edit the parameters passed to the create method within the expectation.
This time, edit it "is invalid without a firstname" and give :firstname
a non-nil value. The spec should fail; replace it with nil to see it pass again.
http://lmws.net/describe-vs-context-in-rspec
Colophon
The cover image of a practical, reliable, red pickup truck is by iStockphoto
contributor Habman_18. I spent a lot of time reviewing photos for the covertoo
much time, probablybut picked this one because it represents my approach to Rails
testingnot flashy, and maybe not always the fastest way to get there, but solid and
dependable. And its red, like Ruby. Maybe it should have been green, like a passing
spec? Hmm.
http://www.istockphoto.com/stock-photo-16071171-old-truck-in-early-morning-light.php?st=1e7555f
http://www.istockphoto.com/user_view.php?id=4151137