Using RSpec Stub and Mock

Written by: Clemens Helm

9 min read

https://fast.wistia.com/embed/medias/3vnik1mfu7.jsonphttps://fast.wistia.com/assets/external/E-v1.js

This is the ninth Testing Tuesday episode. Every week we will share our insights and opinions on the software testing space. Drop by every Tuesday to learn more! Last week we talked about Behavior-Driven Integration and Unit Testing with Cucumber and RSpec.


Specifying examples with RSpec

When you work Outside-In with Behavior-Driven Development your scenarios take care of checking that your application works from a user's perspective. They don't care at all how you implement something.

Let's say we refactor a class of our application. Then we permanently want to make sure that we didn't break anything by moving things around. Unfortunately this is impossible with integration tests, because they are just too slow. This is why we write examples that explain how our single components – like for example classes – should behave. These examples should be fast, so we can run them continuously while we make changes to a class. And they should only specify behavior of the own scope and ignore all behavior of other classes.

I am working in a Ruby on Rails App so I use RSpec to write these examples in this screencast, but there are Spec frameworks for a lot of other languages that work similar. Check out the further info section below to find out more.

Stubbing and Mocking with RSpec

Stubbing and Mocking makes your component examples independent of other components. You can stub methods on objects to let them return whatever you like. And you can use mock objects to replace instances of other classes. This screencast shows how to stub and mock using RSpec.

Up next Testing Tuesday:

Stubbing and Mocking also facilitate designing software. They let you specify the code you wish you had without relying on code that's already there. This makes you build better designed and more readable code. Check back next Testing Tuesday to find out how!

Further info:

Transcript

Ahoi and welcome! They call me Clemens Helm and this is Codeship Testing Tuesday #9. Last week we had a look at integration testing and unit testing. We learned how to write code outside-in with Behavior-Driven development and I also scratched the surface of specifying examples for single components of your application. We'll go into more detail on this today: I'll show you how to get most out of Rspec with Stubbing and Mocking and why RSpec examples are the perfect companions for your integration tests.

When you work Outside-In with Behavior-Driven Development, your Scenarios take care of checking that your application works from a user's perspective. They don't care at all how you implement something. These Scenarios are great for verifying that your application still works after you made a change. But they have two major disadvantages:

  1. Debugging (Working title)

When something breaks, it can be tricky to find out where the root of the problem is. Your failing scenario might show you an error message with a stack trace, but often the trouble is an entirely different part of your application. That's when you have to read through logs and start cumbersome debugging sessions.

  1. Speed

Running your Scenarios is slow. They test your whole application stack and sometimes they even use a browser to check Javascript functionality. If we run all scenarios of the Codeship web application, this will take more than half an hour.

So integration tests are great for verifying that everything works as it should. But let's say we refactor a class of our application. Then we permanently want to make sure that we didn't break anything by moving things around. Unfortunately this is impossible with integration tests, because they are just too slow.

This is why we write examples, how our single components – like for example classes – should behave. These examples should be fast, so we can run them continuously while we make changes to a class. And they should only specify behavior of the own scope and ignore all behavior of other classes. Our scenarios are responsible for making sure that all our components work together, so we don't need to check that in our component examples.

To write these examples we use RSpec here, but there are spec frameworks for a lot of other languages that work similar. Check out the further info section below to find out more.

Let’s say we’ve got an application like the Codeship, where a user has many projects. A user also has a full name, consisting of the first name and the last name. And we've got a project that belongs to a user.

App:

class User
  has_many :projects
  def full_name
    "#{first_name} #{last_name}"
  end
end
class Project
  belongs_to :user
end

When the project name is not set, we'd like to have a placeholder containing the user's full name. So we write a scenario:

Scenario: Placeholder for projects without name
  Given I'm signed up as "Peter Parker"
  When I create a project without a name
  Then the project should be called "Peter Parker's project"

These are the step definitions for the scenario:

Given(/I'm signed up as ".*"/) do |user_name|
  first_name, last_name = user_name.split " "
  visit sign_up_path
  fill_in :first_name, with: first_name
  fill_an :last_name, with: last_name
  click_button :sign_up
end
When(/I create a project without a name/) do
  visit projects_path
  click_link "New project"
  fill_in :name, with: ""
  click_button :commit
end
Then(/the project should be called ".*"/) do |project_name|
  find(".project").should have_content project_name
end

When we run this scenario, it fails, because the project name is empty. Let's fix this by writing an example for the project:

describe Project do
  it "should be named after the user if no name is set" do
    user = User.new first_name: "Peter", last_name: "Parker"
    project = Project.new
    project.user = user
    project.full_name.should == "Peter Parker's project"
  end
end

The Spec fails, but it's quite easy to fix it:

def name
  read_attribute(:name) || "Peter Parker's project"
end

And now the example and the scenario succeed! But wait a minute, we filled in "Peter Parker", so now the project is called "Peter Parker's project", no matter what user it belongs to. We strictly followed the guidelines of Behavior-Driven Development: Make the example pass with the least effort. So the next step would be to write the same scenario and the same example with a different name. In simple cases like this, I usually follow a different approach: I use random values for my examples.

I use a helper method named "random_name" to make this work:

describe Project do
  it "should be named after the user if no name is set" do
    first_name, last_name = 2.times.map { random_name }
    user = User.new first_name: first_name, last_name: last_name
    project = Project.new
    project.user = user
    project.full_name.should == "#{first_name} #{last_name}'s project"
  end
  def random_name
    ('a'..'z').to_a.sample(5).join
  end
end

This helper method generates a random name of 5 letters (show on irb shell). Let's put this helper into our spec_helper file, so other examples can use it as well.

Now our spec fails (show output). To make it work, we need to fill in the user's full name:

def name
  read_attribute(:name) || "#{user.full_name} project"
end

So now it works! But what's the problem with this example? We're not only testing the project's behavior, but also the user's. When we change the implementation of the User's full_name, our Spec will fail:

def full_name
  "#{last_name}, #{first_name}"
end

This is unexpected, because the project is still named after the user, so actually the Spec should still work. When we have a large example suite, these dependencies can cause a lot of work. One little change could lead to dozens of failing specs throughout our whole application. This also means that we would have to run all our examples after every change to make sure we didn't break anything. But wasn't this our problem with integration tests that we wanted to solve by specifying little examples?

As long as there are dependencies on other components in our examples, we will always run into this kind of problems. So how can we resolve them?

By stubbing all behavior of other classes the way we expect it to work. Let's change our example to use stubbing:

describe Project do
  it "should be named after the user if no name is set" do
    full_name = random_name
    user = User.new
    user.stub full_name: full_name
    project = Project.new
    project.user = user
    project.full_name.should == "#{full_name}'s project"
  end
  def random_name
    ('a'..'z').to_a.sample(5).join
  end
end

Stubbing simulates a method by simply returning the value we've specified. So our user's full_name method is never called and this way we're independent of its implementation in this example. But there is one more dependency on the user: If we call User.new, this will invoke the constructor method of the user, which could also make our example fail. In fact, we don't actually need anything from our user but its full name.

So we can just mock our user model instead. A mock is an object that we can use in behalf of another object. It has got only the functionality we assign it:

it "should be named after the user if no name is set" do
  full_name = random_name
  user = mock_model User, full_name: full_name
  project = Project.new
  project.user = user
  project.full_name.should == "#{full_name}'s project"
end

mock_model is RSpecs way of simulating a model. The advantage is that we can use it also as an association. There's a more generic way of mocking an object: a double.

it "should be named after the user if no name is set" do
  full_name = random_name
  user = double full_name: full_name
  project = Project.new
  project.user = user
  project.full_name.should == "#{full_name}'s project"
end

The example fails now, because we can't use a generic double as an association. However, we can just stub the association as well to make it work:

it "should be named after the user if no name is set" do
  full_name = random_name
  project = Project.new
  project.stub user: double(full_name: full_name)
  project.full_name.should == "#{full_name}'s project"
end

This is the version of the example that I would prefer, because it is concise and has no dependencies on other components.

Stubbing also helps us avoid database queries. Database queries are very slow in comparison to application code and they also add a dependency to our components, so we want to avoid them wherever possible. Consider this example:

it "should list project names" do
  number_names = rand 10
  names = number_names.times.map { random_name }
  user = User.create!
  names.each { |name| user.projects.create! name: name }
  user.project_names.should == names
end

Here we are creating a user and several associated projects in the database to prove that project names is a list of all projects' names. But we neither need the database nor do we need projects in this example. We just need a list of objects with a name:

it "should list project names" do
  number_names = rand 10
  names = number_names.times.map { random_name }
  user = User.new
  user.stub projects: names.map { |name| double name: name }
  user.project_names.should == names
end

So in this example we didn't need the database at all and we also didn't need any project instances. The only component that was tested was the user class.

But isn’t it dangerous to work with mocks? In the end we never know in our examples, if the class we're mocking will behave the same way as the mock! What if we assume something that doesn't work the way we mock it?

Well, that's what our integration tests are made for. Running our integration tests makes sure that everything we assumed in our component examples holds true in the "real" world. This is especially great for stubbing behavior that doesn't even exist yet. Your integration tests will always remind you to implement this missing behavior.

But we'll look into this in more detail next week! I will show you how to design your application properly using Behavior-Driven development with RSpec. I hope you liked this episode, and as always I’m looking forward to your comments! Special thanks to Amrit who suggested the topic for today's episode. Have a beautiful week and see you again next Testing Tuesday. … fade out … Oh, there’s one more thing: Always stay shipping.

Further info:

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.