The Rails 4 Way (2014)
Chapter 21. RSpec
I do not think there is any thrill that can go through the human heart like that felt by the inventor as he sees some creation of the brain unfolding to success.
—Nikola Tesla
RSpec is a Ruby domain-specific language for specifying the desired behavior of Ruby code. Its strongest appeal is that RSpec scripts (or simply specs) can achieve a remarkable degree of readability, letting the authors express their intention with greater readability and fluidity than is achievable using ActiveSupport::TestCase style methods and assertions.
RSpec::Rails, a drop-in replacement for the Rails testing subsystem supplies verification, mocking, and stubbing features customized for use with Rails models, controllers, and views. Since switching to RSpec, I have never needed to touch ActiveSupport::TestCase for anything significant again. RSpec is simply that good.
21.1 Introduction
Since RSpec scripts are so readable, I can’t really think of a better way of introducing you to the framework than to dive into an actual spec. Listing 21.1 is part of a real-world RSpec script defining the behavior of a Payment in a Hashrocket client project named Workbeast.com. As you’re reading the spec, let the descriptions attached to the blocks of code come together to form sentences that describe the desired behavior.
Listing 21.1: Excerpt of Workbeast.com’s timesheet spec
1 require 'spec_helper'
2
3 describe Timesheet do
4 subject(:timesheet) { FactoryGirl.build(:timesheet) }
5
6 describe "validation of hours worked" do
7 it "fails without a number" do
8 timesheet.hours_worked = 'abc'
9 expect(timesheet.error_on(:hours_worked).size).to eq(1)
10 end
11
12 it "passes with a number" do
13 timesheet.hours_worked = '123'
14 expect(timesheet.error_on(:hours_worked)).to be_empty
15 end
16
17 end
18
19 context "when submitted" do
20 it "sends an email notification to the manager" do
21 expect(Notifier).to receive(:send_later).
22 with(:deliver_timesheet_submitted, timesheet)
23 timesheet.submit
24 end
25
26 it "notifies its opening" do
27 expect(timesheet.opening).to_not be_nil
28 expect(timesheet.opening).to receive(:fill)
29 timesheet.submit
30 end
31 end
32 end
In the example, the fragment
1 describe Timesheet do
2 subject(:timesheet) { FactoryGirl.build(:timesheet) }
3
4 describe "validation of hours worked" do
5 it "fails without a number" do
6 timesheet.hours_worked = 'abc'
7 expect(timesheet.error_on(:hours_worked).size).to eq(1)
8 end
…should be understood to mean “Timesheet validation of hours worked fails without a number.”
RSpec scripts are collections of behaviors, which in turn have collections of examples. The describe method creates a Behavior object under the covers. The behavior sets the context for a set of specification examples defined with the it method, and you should pass a sentence fragment that accurately describes the context you’re about to specify.
You can use RSpec to specify and test model and controller classes, as well as view templates, as individual units in isolation, like we did in Listing 21.1. RSpec can also be used to create integration tests that exercise the entire Rails stack from top to bottom.
1 feature "Search Colleagues" do
2 let(:user) { FactoryGirl.create(:user, name: 'Joe') }
3
4 let(:public_user) do
5 FactoryGirl.create(:user, name: 'Pete', privacy_level: 'Public')
6 end
7
8 let(:private_user) do
9 FactoryGirl.create(:user, name: 'Nancy', privacy_level: 'Private')
10 end
11
12 background { login_as user }
13
14 scenario "takes you to the search results page" do
15 email_search_for(user, public_user.email)
16 expect(current_path).to eq(search_colleagues_path)
17 end
18
19 scenario "doesn't return the current user" do
20 email_search_for(user, user.email)
21 expect(page).to_not have_content(user.name)
22 end
23
24 scenario "doesn't return private users" do
25 email_search_for(user, private_user.email)
26 expect(page).to_not have_content(private_user.name)
27 end
28
29 context "when the user is not their colleague" do
30 scenario "shows the 'Add colleague' button" do
31 email_search_for(user, FactoryGirl.create(:user).email)
32 expect(page).to have_button('#add-colleague',
33 text: 'Add as Colleague')
34 end
35 end
36
37 def email_search_for(current_user, email)
38 visit colleagues_path
39 fill_in 'Search', with: email
40 click_button 'Search'
41 end
42 end
Use of methods such as visit and fill_in, as well as the checking the contents of objects such as page, hint at what this spec is doing: running your entire Rails application.
Capybara
Note that the “feature” DSL provided by RSpec demonstrated in the example above is is dependent on the Capybara115 gem version 2.0 or later.
21.2 Basic Syntax and API
Let’s run through some of the basic syntactical features of RSpec, which we’ve just encountered in the code listings. RSpec is essentially a domain-specific language for creating specifications. The following API methods form the vocabulary of the language.
21.2.1 describe and context
The describe and context methods are used to group together related examples of behavior. They are aliases, both taking a string description as their first argument and a block to define the context of their scope.
When writing model specs or anything that smacks of a unit test, you can pass a Ruby class as the first argument to describe. Doing so also creates an implicit subject for the examples, which we’ll hold off on explaining for the moment. (If you’re impatient, you can jump ahead in this section to the subject method heading.)
1 describe Timesheet do
2 let(:timesheet) { FactoryGirl.create(:timesheet) }
21.2.2 let(:name) { expression }
The let method simplifies the creation of memoized attributes for use in your spec. Memoized means that the code block associated with the let is executed once and stored for future invocations, increasing performance. Use of let also allows you to lessen your dependence on instance variables, by creating a proper interface to the attributes needed in the spec.
So, why use the let method? Let’s step through a typical spec coding session to understand the motivation. Imagine that you’re writing a spec, and it all starts simply enough with a local blog_post variable.
1 describe BlogPost do
2 it "does something" do
3 blog_post = BlogPost.new title: 'Hello'
4 expect(blog_post).to ...
5 end
6 end
You continue on, writing another similar example, and you start to see some duplication. The blog_post creation is being done twice.
1 describe BlogPost do
2 it "does something" do
3 blog_post = BlogPost.new title: 'Hello'
4 expect(blog_post).to ....
5 end
6
7 it "does something else" do
8 blog_post = BlogPost.new title: 'Hello'
9 expect(blog_post).to ...
10 end
11 end
So, you refactor the instance creation into a before block, and start using an instance variable in the examples.
1 describe BlogPost do
2 before do
3 @blog_post = BlogPost.new title: 'Hello'
4 end
5
6 it "does something" do
7 expect(@blog_post).to ...
8 end
9
10 it "does something else" do
11 expect(@blog_post).to ...
12 end
13 end
And here comes the punchline: you replace the instance variables with a variable described by a let expression.
1 describe BlogPost do
2 let(:blog_post) { BlogPost.new title: 'Hello' }
3
4 it "does something" do
5 expect(blog_post).to ...
6 end
7
8 it "does something else" do
9 expect(@blog_post).to ...
10 end
11 end
The advantages of using let are mostly in the realm of readability. One, it gets rid of all those instance variables and at-signs blotting your code. Two, gets rid of the before block, which arguably has no business setting up a bunch variables in the first place. And three, it shows you who the players are.’ A set of let blocks at the top of an example group reads like a cast of characters in a playbill. You can always refer to it when you’re deep in the code of an example.
21.2.3 let!(:name) { expression }
There are instances where the lazy evaluation of let will not suffice and you need the value memoized immediately. This is found often in cases of integration testing, and is where let! comes into play.
1 describe BlogPost do
2 let(:blog_post) { BlogPost.create title: 'Hello' }
3 let!(:comment) { blog_post.comments.create text: 'first post' }
4
5 describe "#comment" do
6 before do
7 blog_post.comment("finally got a first post")
8 end
9
10 it "adds the comment" do
11 expect(blog_post.comments.count).to eq(2)
12 end
13 end
14 end
Since the comment block would never have been executed for the first assertion if you used a let definition, only one comment would have been added in this spec even though the implementation may be working. By using let! we ensure the initial comment gets created and the spec will now pass.
21.2.4 before and after
The before (and its reclusive cousin, after) methods are akin to the setup and teardown methods of xUnit frameworks like MiniTest. They are used to set up the state as it should be prior to running an example, and if necessary, to clean up the state after the example has run. None of the example behaviors we’ve seen so far required an after block, because frankly, it’s rare to need after in Rails programming.
Before and after code can be inserted in any describe or context blocks, and by default they execute for each it block that shares their scope.
21.2.5 it
The it method also takes a description plus a block, similar to describe. As mentioned, the idea is to complete the thought that was started in the describe method, so that it forms a complete sentence. Your assertions (aka expectations) will always happen within the context of an it block, and you should try to limit yourself to one expectation per it block.
1 context "when there are no search results" do
2 before do
3 email_search_for(user, '123')
4 end
5
6 it "shows the search form" do
7 expect(current_url).to eq(colleagues_url)
8 end
9
10 it "renders an error message" do
11 expect(page).to have_selector('.error',
12 text: 'No matching email addresses found.')
13 end
14 end
21.2.6 specify
The specify method is simply an alias of the it method. However, it’s mainly used in a different construct to improve readability. Consider the following old-school RSpec example:
1 describe BlogPost do
2 let(:blog_post) { BlogPost.new title: 'foo' }
3
4 it "to not be published" do
5 expect(blog_post).to_not be_published
6 end
7 end
Note how the example says “to not be published” in plain English, and the Ruby code within says essentially the same thing: expect(blog_post).to_not be_published This is a situation where specify comes in handy. Examine the alternative example implementation:
1 describe BlogPost do
2 let(:blog_post) { BlogPost.new title: 'foo' }
3 specify { expect(blog_post).to_not be_published }
4 end
The English phrase has been removed, and the Ruby code has been move into a block passed to the specify method. Since the Ruby block already reads like English, there’s no need to repeat yourself. Especially since RSpec automatically (which is pretty cool) generates English output by inspection. Here’s what the RSpec documentation formatter (--format documentation) output looks like:
BlogPost
should not be published
21.2.7 pending
When you leave the block off of an example, RSpec treats it as pending.
1 describe GeneralController do
2 describe "GET to index" do
3 it "will be implemented eventually"
4 end
5 end
RSpec prints out pending examples at the end of its run output, which makes it potentially useful for tracking work in progress.
Pending:
GeneralController on GET to index will be implemented eventually
# Not yet implemented
# ./spec/controllers/general_controller_spec.rb:6
Finished in 0.00024 seconds
1 example, 0 failures, 1 pending
Randomized with seed 31820
A quick and and easy way to mark existing examples as pending is to prepend the it method with an x, like so:
1 describe GeneralController do
2 describe "on GET to index" do
3 xit "should be successful" do
4 get :index
5 expect(response).to be_successful
6 end
7 end
8 end
This is specially useful for debugging and refactoring.
You can also explicitly create pending examples by inserting a call to the pending method anywhere inside of an example.
1 describe GeneralController do
2 describe "on GET to index" do
3 it "is successful" do
4 pending("not implemented yet")
5 end
6 end
7 end
Interestingly, you can use pending with a block to keep broken code from failing your spec. However, if at some point in the future the broken code does execute without an error, the pending block will cause a failure.
1 describe BlogPost do
2 it "defaults to rating of 3.0" do
3 pending "implementation of new rating algorithm" do
4 expect(BlogPost.new.rating).to eq(3.0)
5 end
6 end
7 end
Pro-tip: you can make all examples in a group pending simply by calling pending once in the group’s before block.
1 describe 'Veg-O-Matic' do
2 before { pending }
3
4 it 'slices' do
5 # will not run, instead displays "slices (PENDING: TODO)"
6 end
7
8 it 'dices' do
9 # will also be pending
10 end
11
12 it 'juliennes' do
13 # will also be pending
14 end
15 end
21.2.8 expect(...).to / expect(...).not_to
As of RSpec 3.0, the preferred way to define positive and negative expectations is to use the new expect syntax. Instead of using should and should_not to set expectations, one uses expect(...).to and expect(...).to_not respectively. Note that the should syntax is still available in RSpec for backwards compatibility, but the team encourages moving over to the expect syntax for new projects.
Although the syntax is different from the older should syntax, the new expect syntax works exactly the same. First, you must pass the value/block you want to execute an expectation against to the expect method. Next, chain a method call to or to_not methods to specify if the expectation is to be positive or negative respectively. Finally, you must pass a matcher to the to/to_not method, which will fail the example if it does not match.
1 expect(page).to have_selector('.error',
2 text: 'No matching email addresses found.')
3
4 # equivalent to
5
6 page.should have_selector('.error',
7 text: 'No matching email addresses found.')
There are several ways to generate expectation matchers and pass them to expect(...).to (and expect(...).to_not):
1 expect(receiver).to eq(expected) # any value
2 # Passes if (receiver == expected)
3
4 expect(receiver).to eql(expected)
5 # Passes if (receiver.eql?(expected))
6
7 expect(receiver).to match(regexp)
8 # Passes if (receiver =~ regexp)
The process of learning to write expectations is probably one of the meatier parts of the RSpec learning curve. One of the most common idioms is expect(...).to eq(...) akin to MiniTest’s assert_equal assertion.
21.2.8.1 change and raise_error
When you expect the execution of a block of code to change a value of an object or throw an exception, then expect with its block syntax is your answer. Here’s an example:
1 expect {
2 BlogPost.create title: 'Hello'
3 }.to change(BlogPost, :count).by(1)
This is just a more readable DSL-style version of the RSpec’s older lambda-based syntax:
1 lambda {
2 BlogPost.create title: 'Hello'
3 }.should change { BlogPost.count }.by(1)
Simply put, expect using a block as input, is an alias of the lambda keyword and the to method is an alias of the should method.
Then comes the change matcher. This is where you inspect the attribute or value that you’re interested in. In our example, we’re making sure that the record was saved to the database, thus increasing the record count by 1.
There are a few different variations on the change syntax. Here’s one more example, where we’re more explicit about before and after values by further chaining from and to methods:
1 describe "#publish!" do
2 let(:blog_post) { BlogPost.create title: 'Hello' }
3
4 it "updates published_on date" do
5 expect {
6 blog_post.publish!
7 }.to change { blog_post.published_on }.from(nil).to(Date.today)
8 end
9 end
Here the published_on attribute is examined both before and after invocation of the expect block. This style of change assertion comes in handy when you want to ensure a precondition of the value. Asserting from guarantees a known starting point.
Besides expecting changes, the other common expectation has to do with code that should generate exceptions:
1 describe "#unpublish!" do
2 context "when brand new" do
3 let(:blog_post) { BlogPost.create title: 'Hello' }
4
5 it "raises an exception" do
6 expect {
7 blog_post.unpublish!
8 }.to raise_error(NotPublishedError, /not yet published/)
9 end
10 end
11 end
In this example, we attempt to “unpublish” a brand new blog post that hasn’t been published yet. Therefore, we expect an exception to be raised.
21.2.9 Implicit Subject
Whether you know it or not, every RSpec example group has a subject. Think of it as the thing being described. Let’s start with an easy example:
1 describe BlogPost do
2 it { is_expected.to be_invalid }
3 end
By convention, the implicit subject here is a BlogPost.new instance. The is_expected call may look like it is being called off of nothing, but actually the call is delegated by the example to the implicit subject. It’s just as if you’d written the expression:
expect(BlogPost.new).to be_invalid
21.2.10 Explicit Subject
If the implicit subject of the example group doesn’t quite do the job for you, you can specify a subject explicitly. For example, maybe we need to tweak a couple of the blog post’s attributes on instantiation:
1 describe BlogPost do
2 subject { BlogPost.new title: 'foo', body: 'bar' }
3 it { is_expected.to be_valid }
4 end
Here we have the same delegation story as with implicit subject. The is_expected.to be_valid call is delegated to the subject.
You can also talk to the subject directly. For example, you may need to invoke a method off the subject to change object state:
1 describe BlogPost do
2 subject { BlogPost.new title: 'foo', body: 'bar' }
3
4 it "sets published timestamp" do
5 subject.publish!
6 expect(subject).to be_published
7 end
8 end
Here we call the publish! method off the subject. Mentioning subject directly is the way we get ahold of that BlogPost instance we set up. Finally, we assert that published? boolean is true.
Kevin says… Although you can explicitly call subject within your specs, it’s not very intention revealing. Instead, use “named subjects”, which allow for a subject to be assigned an intention revealing name. To demonstrate, here is the preceding example using a “named subject”: 1 describe BlogPost do 2 subject(:blog_post) { BlogPost.new title: 'foo', body: 'bar' } 3 4 it "sets published timestamp" do 5 blog_post.publish! 6 expect(blog_post).to be_published 7 end 8 end |
21.3 Matchers
Thanks to method_missing, RSpec can support arbitrary predicates, that is, it understands that if you invoke something that begins with be_, then it should use the rest of the method name as an indicator of which predicate-style method to invoke the target object. (By convention, a predicate method in Ruby ends with a ? and should return the equivalent of true or false.) The simplest hard-coded predicate-style matchers are:
1 expect(target).to be
2 expect(target).to be_true
3 expect(target).to be_truthy # Not nil or false
4 expect(target).to be_false
5 expect(target).to be_falsy # nil or false
6 expect(target).to be_nil
7 expect(target).to_not be_nil
Arbitrary predicate matchers can assert against any target, and even support parameters!
1 expect(thing).to be # passes if thing is not nil or false
2 expect(collection).to be_empty # passes if target.empty?
3 expect(target).to_not be_empty # passes unless target.empty?
4 expect(target).to_not be_under_age(16) # passes unless target.under_age?(16)
As an alternative to prefixing arbitrary predicate matchers with be_, you may choose from the indefinite article versions be_a_ and be_an_, making your specs read much more naturally:
1 expect("a string").to be_an_instance_of(String)
2 expect(3).to be_a_kind_of(Fixnum)
3 expect(3).to be_a_kind_of(Numeric)
4 expect(3).to be_an_instance_of(Fixnum)
5 expect(3).to_not be_instance_of(Numeric) #fails
The cleverness (madness?) doesn’t stop there. RSpec will even understand have_ prefixes as referring to predicates like has_key?:
1 expect({foo: "foo"}).to have_key(:foo)
2 expect({bar: "bar"}).to_not have_key(:foo)
RSpec has a number of expectation matchers for working with classes that implement module Enumerable. You can specify whether an array should include a particular element, or if a string contains a substring. This one always weirds me out when I see it in code, because my brain wants to think that include is some sort of language keyword meant for mixing modules into classes. It’s just a method, so it can be overriden easily.
1 expect([1, 2, 3]).to include(1)
2 expect([1, 2, 3]).to_not include(4)
3 expect("foobar").to include("bar")
4 expect("foobar").to_not include("baz")
RSpec also includes a range matcher, that can be used to see if a value is covered within a given range.
expect(1..10).to cover(3)
21.4 Custom Expectation Matchers
When you find that none of the stock expectation matchers provide a natural-feeling expectation, you can very easily write your own. All you need to do is write a Ruby class that implements the following two methods:
· matches?(actual)
· failure_message
The following methods are optional for your custom matcher class:
· does_not_match?(actual)
· failure_message_when_negated
· description
The example given in the RSpec API documentation is a game in which players can be in various zones on a virtual board. To specify that a player bob should be in zone 4, you could write a spec like
expect(bob.current_zone).to eq(Zone.new("4"))
However, it’s more expressive to say one of the following, using the custom matcher in Listing 21.2:
Listing 21.2: BeInZone custom expectation matcher class
1 # expect(bob)to be_in_zone(4) and expect(bob).to_not be_in_zone(3)
2 classBeInZone
3 def initialize(expected)
4 @expected = expected
5 end
6
7 def matches?(actual)
8 @actual = actual
9 @actual.current_zone.eql?(Zone.new(@expected))
10 end
11
12 def failure_message
13 "expected #{@actual.inspect} to be in Zone #{@expected}"
14 end
15
16 def failure_message_when_negated
17 "expected #{@actual.inspect} not to be in Zone #{@expected}"
18 end
19 end
In addition to the matcher class you would need to write the following method so that it’d be in scope for your spec.
1 def be_in_zone(expected)
2 BeInZone.new(expected)
3 end
This is normally done by including the method and the class in a module, which is then included in your spec.
1 describe "Player behaviour" do
2 include CustomGameMatchers
3 ...
4 end
Or you can include helpers globally in a spec_helper.rb file required from your spec file(s):
1 RSpec.configure do |config|
2 config.include CustomGameMatchers
3 end
21.4.1 Custom Matcher DSL
RSpec includes a DSL for easier definition of custom matchers. The DSL’s directives match the methods you implement on custom matcher classes. Just add code similar to the following example in a file within the spec/support directory.
1 require 'nokogiri'
2
3 RSpec::Matchers.define :contain_text do |expected|
4 match do |response_body|
5 squished(response_body).include?(expected.to_s)
6 end
7
8 failure_message do |actual|
9 "expected the following element's content to include
10 #{expected.inspect}:\n\n#{response_text(actual)}"
11 end
12
13 failure_message_when_negated do |actual|
14 "expected the following element's content to not
15 include #{expected.inspect}:\n\n#{squished(actual)}"
16 end
17
18 def squished(response_body)
19 Nokogiri::XML(response_body).text.squish
20 end
21 end
21.4.2 Fluent Chaining
You can create matchers that obey a fluent interface using the chain method:
1 RSpec::Matchers.define(:tip) do |expected_tip|
2 chain(:on) do |bill|
3 @bill = bill
4 end
5
6 match do |person|
7 person.tip_for(@bill) == expected_tip
8 end
9 end
This matcher can be used as follows:
1 describe Customer do
2 it { is_expected.to tip(10).on(50) }
3 end
In this way, you can begin to create your own fluent domain-specific languages for testing your complex business logic in a very readable way.
21.5 Shared Behaviors
Often you’ll want to specify similar behavior in multiple specs. It would be silly to type out the same code over and over. Fortunately, RSpec has a feature named shared behaviors that aren’t run individually, but rather are included into other behaviors; they are defined using shared_examples.
1 shared_examples "a phone field" do
2 it "has 10 digits" do
3 business = Business.new(phone_field: '8004567890')
4 expect(business.errors_on(:phone_field)).to be_empty
5 end
6 end
7
8 shared_examples "an optional phone field" do
9 it "handles nil" do
10 business = Business.new phone_field: nil
11 expect(business.attributes[phone_field]).to be_nil
12 end
13 end
You can invoke a shared example using the it_behaves_like method, in place of an it.
1 describe "phone" do
2 let(:phone_field) { :phone }
3 it_behaves_like "a phone field"
4 end
5
6 describe "fax" do
7 let(:phone_field) { :fax }
8 it_behaves_like "a phone field"
9 it_behaves_like "an optional phone field"
10 end
You can put the code for shared examples almost anywhere, but the default convention is to create a file named spec/support/shared_examples.rb to hold them.
21.6 Shared Context
When used in combination, shared_context and include_context allow you to share before/after hooks, subject declarations, let declarations, and method definitions across example groups. This is useful in cases when several examples share some state. To define a shared context, supply a name and block of code to the shared_context macro style method.
1 shared_context 'authenticated user' do
2 let(:current_user) { FactoryGirl.create(:user) }
3
4 before do
5 sign_in current_user
6 end
7 end
To include a shared context in your examples, use the include_context macro style method.
1 context "user is authenticated" do
2 include_context 'authenticated user'
3 ...
4 end
The recommended location to place shared_context definitions is in the spec/support directory.
21.7 RSpec’s Mocks and Stubs
It’s possible to use a number of mocking frameworks including Mocha, Flexmock, RR, and more. In our examples, however, we’ll use RSpec’s own mocking and stubbing facilities, which are almost the same and equally powerful. Mostly the method names vary.116
Read Martin Fowler’s explanation at http://www.martinfowler.com/articles/mocksArentStubs.html.
21.7.0.1 Test Doubles
A test double is an object that stands in for another in your system during a code example. To create a test double object, you simply call the double method anywhere in a spec, and give it a name as an optional parameter. It’s a good idea to give test double objects a name if you will be using more than one of them in your spec. If you use multiple anonymous test doubles, you’ll probably have a hard time telling them apart if one fails.
echo = double('echo')
With a test double, you can set expectations about what messages are sent to your test double during the course of your spec (commonly known as a mock). Test doubles with message expectations will cause a spec to fail if those expectations are not met. To set an expectation on a test double, we invoke receive.
expect(echo).to receive(:sound)
The chained method with is used to define expected parameters. If we care about the return value, we chain and_return at the end of the expectation or use a block.
1 expect(echo).to receive(:sound).with("hey").and_return("hey")
2 expect(echo).to receive(:sound).with("hey") { "hey" }
Note
In older versions of RSpec, you would define mock and stub objects via the mock and stub methods respectively. Although these methods are still available in RSpec 3.0, they are available only for backwards compatibility and may be removed in a future version.
21.7.0.2 Null Objects
Occasionally you just want an object for testing purposes that accepts any message passed to it—a pattern known as null object. It’s possible to make one using the as_null_object method with a test double object.
null_object = double('null').as_null_object
21.7.0.3 Method Stubs
You can easily create a stub object in RSpec via the double factory method. You pass stub a name and default attributes as a hash.
yodeler = double('yodeler', yodels?: true)
By the way, there’s no rule that the name parameter of a mock or stub needs to be a string. It’s pretty typical to pass double a class reference corresponding to the real type of object.
yodeler = double(Yodeler, yodels?: true)
21.7.0.4 Partial Mocking and Stubbing
You can install or replace methods on any object, not just doubles, with a technique called partial mocking and stubbing. RSpec supports the following two formats for declaring method stubs on existing objects:
1 allow(invoice).to receive(:hourly_total) { 123.45 }
2 allow(invoice).to receive(:billed_expenses).and_return(543.21)
Even though RSpec’s authors warn us about partial stubbing in their docs, the ability to do it is really useful in practice.
21.7.0.5 receive_message_chain
It’s really common to find yourself writing some gnarly code when you rely on double to spec behavior of nested method calls.117 But, sometimes you need to stub methods down a dot chain, where one method is invoked on another method, which is itself invoked on another method, and so on. For example, you may need to stub out a set of recent, unpublished blog posts in chronological order, like BlogPost.recent.unpublished.chronological
Try to figure out what’s going on in the following example. I bet it takes you more than a few seconds!
1 allow(BlogPost).to receive(:recent).
2 and_return(double(unpublished: double(chronological: [double,
3 double, double])))
That example code can be factored to be more verbose, which makes it a little easier to understand, but is still pretty bad.
1 chronological = [double, double, double]
2 unpublished = double(chronological: chronological)
3 recent = double(unpublished: unpublished)
4 allow(BlogPost).to receive(recent).and_return(recent)
Luckily, Rspec gives you the receive_message_chain method, which understands exactly what you’re trying to do here and dramatically simplifies the code needed:
allow(BlogPost)
.to receive_message_chain(:recent, :unpublished, :chronological)
.and_return([double, double, double])
However, just because it’s so easy to stub the chain, doesn’t mean it’s the right thing to do. The question to ask yourself is, “Why am I testing something related to methods so deep down a chain? Could I move my tests down to that lower level?” Demeter would be proud.
21.8 Running Specs
Specs are executable documents. Each example block is executed inside its own object instance, to make sure that the integrity of each is preserved (with regard to instance variables, etc.).
If I run one of the Workbeast specs using the rspec command that should have been installed on my system by the RSpec gem, I’ll get output similar to that of Test::Unit—familiar, comfortable, and passing. Just not too informative.
$ rspec spec/models/colleague_import_spec.rb
.........
Finished in 0.330223 seconds
9 examples, 0 failures
RSpec is capable of outputting results of a spec run in many formats. The traditional dots output that looks just like Test::Unit is called progress and, as we saw a moment ago, is the default. However, if we add the -fd command-line parameter to rspec, we can cause it to output the results of its run in a very different and much more interesting format, the documentation format.
$ rspec -fd spec/models/billing_code_spec.rb
BillingCode
has a bidirectional habtm association
removes bidirectional association on deletion
Finished in 0.066201 seconds
2 examples, 0 failures
Nice, huh? If this is the first time you’re seeing this kind of output, I wouldn’t be surprised if you drifted off in speculation about whether RSpec could help you deal with sadistic PHB-imposed118 documentation requirements.
Having these sorts of self-documenting abilities is one of the biggest wins you get in choosing RSpec. It compels many people to work toward better spec coverage of their project. I also know from experience that development managers tend to really appreciate RSpec’s output, even to the extent of incorporating it into their project deliverables.
Besides the different formatting, there are all sorts of other command-line options available. Just type rspec --help to see them all.
That does it for our introduction to RSpec. Now we’ll take a look at using RSpec with Ruby on Rails.
21.9 RSpec Rails Gem
The RSpec Rails gem provides four different contexts for specs, corresponding to the four major kinds of objects you write in Rails. Along with the API support you need to write Rails specs, it also provides code generators and a bundle of Rake tasks.
21.9.1 Installation
Assuming you have the rspec-rails gem bundled already, you should run the rspec:install generator provided to set up your project for use with RSpec.
$ rails generate rspec:install
create .rspec
create spec
create spec/spec_helper.rb
The generator will add the files and directories necessary to use RSpec with your Rails project.
21.9.1.1 RSpec and Rake
The rspec.rake script sets the default Rake task to run all specs in your /spec directory tree. It also creates specific rake spec tasks for each of the usual spec directories.
$ rake -T spec
rake spec # Run all specs in spec directory (excluding plugin specs)
rake spec:controllers # Run the code examples in spec/controllers
rake spec:helpers # Run the code examples in spec/helpers
rake spec:lib # Run the code examples in spec/lib
rake spec:mailers # Run the code examples in spec/mailers
rake spec:models # Run the code examples in spec/models
rake spec:requests # Run the code examples in spec/requests
rake spec:routing # Run the code examples in spec/routing
rake spec:views # Run the code examples in spec/views
21.9.1.2 RSpec and Generators
RSpec ensures that other generators in your project are aware of it as your chosen test library. Subsequently it will be used for command-line generation of models, controllers, etc.
$ rails generate model Invoice
invoke active_record
create db/migrate/20100304010121_create_invoices.rb
create app/models/invoice.rb
invoke rspec
create spec/models/invoice_spec.rb
21.9.1.3 RSpec Options
The .rspec file contains a list of default command-line options. The generated file looks like
--color
--format progress
You can change it to suit your preference. I like my spec output in color, but usually prefer the more verbose output of --format documentation.
Tim says… I go back and forth between preferring the dots of the progress format and the verbose output of the documentation format. With the more verbose output and long spec suites, it’s easy to miss if something failed if you look away from your screen. Specially on terminals with short buffers. |
Here are some additional options that you might want to set in your .rspec
--fail-fast Tells RSpec to stop running the test suite on the
first failed test
-b, --backtrace Enable full backtrace
-p, --profile Enable profiling of examples w/output of top 10 slowest
examples
21.9.1.4 The RSpec Helper Script
As opposed to command-line options, major settings and configuration of your spec suite are kept in spec/spec_helper.rb, which is always required at the top of an RSpec spec.
A boilerplate copy is generated by default when you install RSpec into your project. Let’s go through it section by section and cover what it does.
First of all, we ensure that the Rails environment is set to test. Remember that RSpec replaces the standard MiniTest-based suite that is generated by default for Rails apps.
ENV["RAILS_ENV"] ||= 'test'
Next the Rails environment and RSpec Rails are loaded up.
1 require File.expand_path("../../config/environment", __FILE__)
2 require 'rspec/rails'
RSpec has the notion of supporting files containing custom matchers and any other code that helps setup additional functionality for your spec suite, so it scans the spec/support directory to find those files, akin to Rails initializers.
1 # Requires supporting files with custom matchers and macros, etc,
2 # in ./support/ and its subdirectories.
3 Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
If Active Record is being utilized in the project, RSpec will check to see if there are any pending migrations before the tests are run.
1 # Checks for pending migrations before tests are run.
2 # If you are not using ActiveRecord, you can remove this line.
3 ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
Finally, there is a block of configuration for your spec suite where you can set fixture paths, transaction options, and mocking frameworks.
1 RSpec.configure do |config|
2 # ## Mock Framework
3 #
4 # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
5 #
6 # config.mock_with :mocha
7 # config.mock_with :flexmock
8 # config.mock_with :rr
9
10 # Remove this line if you're not using ActiveRecord or ActiveRecord
11 # fixtures
12 config.fixture_path = "#{::Rails.root}/spec/fixtures"
13
14 # If you're not using ActiveRecord, or you'd prefer not to run each of
15 # your examples within a transaction, remove the following line or assign
16 # false instead of true.
17 config.use_transactional_fixtures = true
18
19 # Run specs in random order to surface order dependencies. If you find an
20 # order dependency and want to debug it, you can fix the order by
21 # providing the seed, which is printed after each run.
22 # --seed 1234
23 config.order = "random"
24 end
Tim says…. Traditionally a lot of extra helper methods were put into the spec_helper file, hence its name. However, nowadays it’s generally easier to organize your additions in spec/support files, for the same reasons config/initializers can be easier to manage than sticking everything in config/environment.rb. While we’re on the subject, keep in mind that any methods defined at the top level of a support file will become global methods available from all objects, which almost certainly not what you want. Instead, create a module and mix it in, just like you’d do in any other part of your application. 1 moduleAuthenticationHelpers 2 def sign_in_as(user) 3 # ... 4 end 5 end 6 7 Rspec.configure do |config| 8 config.include AuthenticationHelpers 9 end |
21.9.2 Model Specs
Model specs help you design and verify the domain model of your Rails application, both Active Record and your own classes. RSpec Rails doesn’t provide too much special functionality for model specs, because there’s not really much needed beyond what’s provided by the base library. Let’s generate a Schedule model and examine the default spec that is created along with it.
1 $ rails generate model Schedule name:string
2 invoke active_record
3 create db/migrate/20131202160457_create_schedules.rb
4 create app/models/schedule.rb
5 invoke rspec
6 create spec/models/schedule_spec.rb
The boilerplate spec/models/schedule_spec.rb looks like
1 require 'spec_helper'
2
3 describe Schedule do
4 pending "add some examples to (or delete) #{__FILE__}"
5 end
Assume for example that our Schedule class has a collection of day objects.
1 classSchedule < ActiveRecord::Base
2 has_many :days
3 end
Let’s specify that we should be able to get a roll-up total of hours from schedule objects. Instead of fixtures, we’ll mock out the days dependency.
1 require 'spec_helper'
2
3 describe Schedule do
4 let(:schedule) { Schedule.new }
5
6 it "should calculate total hours" do
7 days = double('days')
8 expect(days).to receive(:sum).with(:hours).and_return(40)
9 allow(schedule).to receive(:days).and_return(days)
10 expect(schedule.total_hours).to eq(40)
11 end
12 end
Here we’ve taken advantage of the fact that association proxies in Rails are rich objects. Active Record gives us several methods for running database aggregate functions. We set up an expectation that days should receive the sum method with one argument—:hours—and return 40. We can satisfy this specification with a very simple implementation:
1 classSchedule
2 has_many :days
3
4 def total_hours
5 days.sum :hours
6 end
7 end
A potential benefit of mocking the days proxy is that we no longer rely on the database119 in order to write our specifications and implement the total_hours method, which will make this particular spec execute lightning fast.
On the other hand, a valid criticism of this approach is that it makes our code harder to refactor. Our spec would fail if we changed the implementation of total_hours to use Enumerable#inject, even though the external behavior doesn’t change. Specifications are not only describing the visible behavior of objects, but the interactions between an object and its associated objects as well. Mocking the association proxy in this case lets us clearly specify how a Schedule should interact with its Days.
Leading mock objects advocates see mock objects as a temporary design tool. You may have noticed that we haven’t defined the Day class yet. So another benefit of using mock objects is that they allow us to specify behavior in true isolation, and during design-time. There’s no need to break our design rhythm by stopping to create the Day class and database table. This may not seem like a big deal for such a simple example, but for more involved specifications it is really helpful to just focus on the design task at hand. After the database and real object models exist, you can go back and replace the mock days with calls to the real deal. This is a subtle, yet very powerful message about mocks that is usually missed.
21.9.3 Controller Specs
RSpec gives you the ability to specify your controllers either in isolation from their associated views or together with them, as in regular Rails tests. According to the API docs:
Controller Specs support running specs for Controllers in two modes, which represent the tension between the more granular testing common in TDD and the more high-level testing built into rails. BDD sits somewhere in between: we want to achieve a balance between specs that are close enough to the code to enable quick fault isolation and far enough away from the code to enable refactoring with minimal changes to the existing specs.
The controller class is passed to the describe method like
describe MessagesController do
An optional second parameter can be provided to include additional information.
I typically group my controller examples by action and HTTP method. This example requires a logged-in user, so I stub my application controller’s current_user accessor to return a user record via FactoryGirl.
before(:each) do
allow(controller).to receive(:current_user) { FactoryGirl.create(user) }
Next, I create a stubbed factory for Message using the build_stubbed FactoryGirl method. I want this stubbed message to be returned whenever Message.all is called during the spec.
@message = FactoryGirl.build_stubbed(:message)
allow(Message).to receive(:all) { [@message] }
Now I can start specifying the behavior of actions (in this case, the index action). The most basic expectation is that the response should be successful, HTTP’s 200 OK response code.
1 it "is successful" do
2 get :index
3 expect(response.status).to eq(200)
4 end
Additional expectations that should be done for most controller actions include the template to be rendered and variable assignment.
1 it "renders the index template " do
2 get :index
3 expect(response).to render_template(:index)
4 end
5
6 it "assigns the found messages for the view" do
7 get :index
8 expect(assigns(:messages)).to include(@message)
9 end
Previously we saw how to stub out a model’s association proxy. Instead of stubbing the controller’s current_user method to return an actual user from the database, we can have it return a double.
@user = double(User, name: "Quentin")
allow(controller).to receive(:current_user) { @user }
21.9.3.1 Isolation and Integration Modes
By default, RSpec on Rails controller specs run in isolation mode, meaning that view templates are not involved. The benefit of this mode is that you can spec the controller in complete isolation of the view, hence the name. Maybe you can sucker someone else into maintaining the view specs?
That sucker comment is of course facetious. Having separate view specs is not as difficult as it’s made out to be sometimes. It also provides much better fault isolation, which is a fancy way of saying that you’ll have an easier time figuring out what’s wrong when something fails.
If you prefer to exercise your views in conjunction with your controller logic inside the same controller specs, just as traditional Rails functional tests do, then you can tell RSpec on Rails to run in integration mode using the render_views macro. It’s not an all-or-nothing decision. You can specify modes on a per-behavior basis.
describe "Requesting /messages using GET" do
render_views
When you invoke render_views, the controller specs will be executed once with view rendering turned on.
21.9.3.2 Specifying Errors
Ordinarily, Rails rescues exceptions that occur during action processing, so that it can respond with a 501 error code and give you that great error page with the stack trace and request variables, and so on. In order to directly specify that an action should raise an error, you bypass Rails’ default handling of errors and those specified with rescue_from with RSpec method bypass_rescue.
To illustrate, assuming the ApplicationController invokes rescue_from for the exception AccessDenied and redirects to 401.html:
1 classApplicationController < ActionController::Base
2 rescue_from AccessDenied, with: :access_denied
3
4 private
5
6 def access_denied
7 redirect_to "/401.html"
8 end
9 end
then we could test an error was raised for a controller action using bypass_rescue.
1 it "raises an error" do
2 bypass_rescue
3 expect { get :index }.to raise_error(AccessDenied)
4 end
If bypass_rescue was not included in the preceding example, the spec would have failed due to Rails rescuing the exception and redirecting to the page 401.html.
21.9.3.3 Specifying Routes
One of Rails’ central components is routing. The routing mechanism is the way Rails takes an incoming request URL and maps it to the correct controller and action. Given its importance, it is a good idea to specify the routes in your application. You can do this with by providing specs in the spec/routes directory and have two matchers to use, route_to and be_routable.
1 context "Messages routing" do
2 it "routes /messages/ to messages#show" do
3 expect(get: "/messages").to route_to(
4 controller: "articles",
5 action: "index"
6 )
7 end
8
9 it "does not route an update action" do
10 expect(post: "/messages").to_not be_routable
11 end
12 end
21.9.4 View Specs
Controller specs let us integrate the view to make sure there are no errors with the view, but we can do one better by specifying the views themselves. RSpec will let us write a specification for a view, completely isolated from the underlying controller. We can specify that certain tags exist and that the right data is outputted.
Let’s say we want to write a page that displays a private message sent between members of an internet forum. RSpec creates the spec/views/messages directory when we use the controller generator. The first thing we would do is create a file in that directory for the show view, naming itshow.html.haml_spec.rb. Next we would set up the information to be displayed on the page.
1 describe "messages/show.html.haml" do
2 before(:each) do
3 @message = FactoryGirl.build_stubbed(:message, subject: "RSpec rocks!")
4
5 sender = FactoryGirl.build_stubbed(:person, name: "Obie Fernandez")
6 expect(@message).to receive(:sender).and_return(sender)
7
8 recipient = FactoryGirl.build_stubbed(:person, name: "Pat Maddox")
9 expect(@message).to receive(:recipient).and_return(recipient)
If you want to be a little more concise at the cost of one really long line of code that you’ll have to break up into multiple lines, you can create the mocks inline like:
1 describe "messages/show.html.haml " do
2 before(:each) do
3 @message = FactoryGirl.build_stubbed(:message,
4 subject: "RSpec rocks!",
5 sender: FactoryGirl.build_stubbed(:person, name: "Obie Fernandez"),
6 recipient: FactoryGirl.build_stubbed(:person, name: "Pat Maddox"))
Either way, this is standard mock usage similar to what we’ve seen before. Again, mocking the objects used in the view allows us to completely isolate the specification.
21.9.4.1 Assigning Instance Variables
We now need to assign the message to the view. The rspec_rails gem gives us a method named assign method to do just that.
assign(:message, @message)
Fantastic! Now we are ready to begin specifying the view page. We’d like to specify that the message subject is displayed, wrapped in an <h1> tag. The Capybara expectation have_selector takes two arguments—the tag selector and a hash of options such as :text.
1 it "displays the message subject" do
2 render "messages/show"
3 expect(rendered).to have_selector('h1', text: 'RSpec rocks!')
4 end
HTML tags often have an ID associated with them. We would like our page to create a <div> with the ID message_info for displaying the sender and recipient’s names. We can pass the ID to have_selector as well.
1 it "displays a div with id message_info" do
2 render "messages/show"
3 expect(rendered).to have_selector('div#message_info')
4 end
21.9.5 Helper Specs
It’s really easy to write specs for your custom helper modules. Just pass describe to your helper module and it will be mixed into a special helper object in the spec class so that its methods are available to your example code.
1 describe ProfileHelper do
2 it "profile_photo should return nil if user's photos is empty" do
3 user = mock_model(User, photos: [])
4 expect(helper.profile_photo(user)).to be_nil
5 end
6 end
21.9.6 Feature Specs
A well-written acceptance test suite is an essential ingredient in the success of any complex software project, particularly those run on Agile principles and methodologies, such as extreme programming. One of the best definitions for an acceptance test is from the Extreme Programming official website:
The customer specifies scenarios to test when a user story has been correctly implemented. A story can have one or many acceptance tests, what ever it takes to ensure the functionality works.120
Stated simply, acceptance tests let us know that we are done implementing a given feature, or user story, in XP lingo. Incidentally, RSpec ships with a DSL that allows defining examples using the same XP lingo we are used to. Instead of using the describe method to group together related examples of behavior, we use feature. To specify a scenario for a given feature, we use the scenario method with a description instead of it. Although these methods are simply aliases for existing RSpec methods, they add a level of readability and provide an visual differentiator from isolated RSpec examples.
1 require 'spec_helper'
2
3 feature "Some Awesome Feature" do
4 background do
5 # Setup some common state for all scenarios
6 # same as `before(:each)`
7 end
8
9 scenario "A feature scenario" do
10 ...
11 end
12 end
All feature specs should be located in the spec/features directory.
21.9.6.1 Getting Started
To use RSpec’s feature DSL for acceptance tests, you must first add the capybara gem to your application’s Gemfile under the test group and run bundle.
1 # Gemfile
2
3 group :test do
4 gem 'capybara', '~> 2.2.0'
5 ...
6 end
To enable Capybara in RSpec, in your spec/spec_helper.rb file, require capybara/rspec:
require 'capybara/rspec'
21.9.6.2 Using Capybara
Capybara provides a DSL that allows you to interact with your application as you would via a web browser.
1 require 'spec_helper'
2
3 feature 'Authentication' do
4 let(:email) { 'bruce@wayneenterprises.com' }
5 let(:password) { 'batman' }
6
7 scenario "signs in with correct credentials" do
8 FactoryGirl.create :user, email: email, password: password
9 visit(new_user_session_path)
10 fill_in 'Email', with: email
11 fill_in 'Password', with: password
12 click_on 'Sign in'
13 expect(current_path).to eq(dashboard_path)
14 expect(page).to have_content('Signed in successfully')
15 end
16
17 ...
18 end
Navigating to a web page using Capybara is done via the visit method. The method will perform a GET request on the supplied path.
visit('/dashboard')
visit(new_user_session_path)
To interact with a web page, Capybara provides action methods that allow the clicking of buttons or links, and the ability to fill-in forms. The following are a listing of action methods you can expect to find in a Capybara feature spec:
attach_file('Image', '/path/to/image.jpg')
check('A Checkbox')
choose('A Radio Button')
click_link('Link Text')
click_button('Save')
click_on('Link Text') # a link or a button
fill_in('Name', with: 'Bruce')
select('Option', from: 'Select Box')
uncheck('A Checkbox')
For a full reference of each action method, see the Capybara official documentation.121
Finally, Capybara provides various matchers to assert a page contains a CSS selector, a XPath, or content.
expect(page).to have_selector('header h1')
expect(page).to have_css('header h1')
expect(page).to have_selector(:xpath, '//header/h1')
expect(page).to have_xpath('//header/h1')
expect(page).to have_content('TR4W')
21.9.6.3 Capybara Drivers
By default, Capybara uses Rack::Test as a headless driver to interact with your web application. It is best suited for acceptance tests that don’t require any outside interaction or JavaScript testing. You can also override the driver Capybara uses through the default_driver configuration setting.
Capybara.default_driver = :selenium
If only some scenarios test JavaScript, you can keep :rack_test as the default driver and explicitly set a driver for JavaScript.
Capybara.javascript_driver = :poltergeist
For any scenarios that require the JavaScript driver, add js: true following the scenario description.
scenario "JavaScript dependent scenario", js: true do
...
end
These driver settings should be set in spec/spec_helper.rb
Database Cleaner
When Capybara runs, it takes care of starting and stopping the HTTP server that will used for testing the application. However, some drivers require an actual HTTP server, such as Selenium and Poltergeist. Those drivers are started in another thread and as a result.
Since the drivers are in another thread, applications that are dependent on a SQL database cannot use the default RSpec strategy of running every test in a transaction. This is because transactions are not shared across threads. If you were to run a Capybara driver like :poltergist in a transaction, any data you set in RSpec for the scenario would not be available to Capybara.
Using the gem database_cleaner, we can configure RSpec to use a truncation strategy instead for JavaScript dependent scenarios. Using truncation, the entire database is emptied out after each test instead of running in a transaction.
1 RSpec.configure do |config|
2 config.before(:suite) do
3 DatabaseCleaner.clean_with(:truncation)
4 end
5
6 config.before(:each) do
7 DatabaseCleaner.strategy = :transaction
8 end
9
10 config.before(:each, js: true) do
11 DatabaseCleaner.strategy = :truncation
12 end
13
14 config.before(:each) do
15 DatabaseCleaner.start
16 end
17
18 config.after(:each) do
19 DatabaseCleaner.clean
20 end
21
22 end
21.10 RSpec Tools
There are several open-source projects that enhance RSpec’s functionality and your productivity or can be used in conjunction with RSpec and other testing libraries.
21.10.1 Guard-RSpec
Guard-RSpec122 is an automated testing framework that runs your spec suite when files are modified.
21.10.2 Spring
As your application grows, an automated test suite can start to slow down your workflow when writing specs at a frequent rate. This is due to the nature of Rails needing to load the environment for each spec run. Spring123 alleviates this by loading the Rails environment only once and having the remaining specs use the preloaded environment. Spring will be included by default in Rails 4.1.
21.10.3 Specjour
Specjour124 is a tool aimed at lowering the run times of your entire spec suite. It distributes your specs over a LAN via Bonjour, running the specs in parallel on the number of workers it finds.
21.10.4 SimpleCov
SimpleCov is a code coverage tool for Ruby.125 You can run it on your specs to see how much of your production code is covered. It provides HTML output to easily tell what code is covered by specs and what isn’t. The results are outputted into a directory named coverage and contain a set of HTML files that you can browse by opening index.html.
21.11 Conclusion
You’ve gotten a taste of the different testing experience that RSpec delivers. At first it may seem like the same thing as MiniTest with some words substituted and shifted around. One of the key points of TDD is that it’s about design rather than testing. This is a lesson that every good TDDer learns through lots of experience. RSpec uses a different vocabulary and style to emphasize that point. It comes with the lesson baked in so that you can attain the greatest benefits of TDD right away.