This is the tenth 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 using stubbing and mocking to specify your examples behavior-driven with RSpec.
Design your code with stubs in RSpec
It's great to be able to stub existing code to become independent from other components' behavior. But it's even better that you can stub code that doesn't exist before you implement it.
Stubbing code that isn't even there yet is a great way of designing your application. Instead of taking the code that already exists, you think of the code you wish you had. Stubs let you specify what you wish your code looked like by simulating methods the way they should behave. Step by step you implement the stubbed methods afterwards, guided by your integration tests that point out what to implement next.
This approach leads to readable, maintainable and reusable code. In this screencast we walk you step by step through this design process.
Up next Testing Tuesday: Reliable mocks with Bogus
In the last two Testing Tuesday episode we made sure that all our stubbed and mocked behavior also works in the "real" world by running our Cucumber scenarios. This can become cumbersome though. A faster approach is to use Bogus, that checks the methods you're stubbing for you. Learn more next Testing Tuesday!
Ahoy and welcome! My name is chief mate Clemens Helm and you're watching Codeship Testing Tuesday #10.
Last week we specified single components of our application using stubbing and mocking. We stubbed existing behavior of other components to become independent of their implementation.
This week I show you how to stub code that isn't even there yet. This will improve the way you design your code.
== Intro End
We'll keep working with last weeks application. We've got a user with a first name, a last name and a full name. And a user has many projects.
And we've got projects that have a name.
Right now, every visitor of our application can access all projects, but we'd like only team members to access their projects.
As usual we write a Cucumber feature first:
Feature: Restricting project access In order to keep a project private As the project's owner I only want team members to access the project Scenario: Accessing a project that I'm not team member of Given I'm signed up as "NSA" When I try to access another user's project Then I should not see the project
And these are our step definitions: We reused the first step from last weeks example. The other two steps are
When(/I try to access another user's project/) do another_user = User.new name: "Another user" @project = Project.new @project.user = another_user @project.save! visit project_path(@project) end Then(/I should not see the project/) do page.current_path.should_not == project_path(@project) end
When we run this feature, it fails, because so far we haven't got any restrictions what should be accessible or not.
Let's write an example for our projects controller to prevent access:
# projects_controller_spec.rb describe ProjectsController do it "should check if a user is team members" do user = double project = mock_model Project ProjectController.any_instance.stub current_user: user Project.stub find: project user.should_receive(:member_of?).with(project) get :show, project_id: project.id end end
Here we use
mock_model to simulate a project because it also creates an id for the project that we can pass to the controller action. We also simulate that there is a logged in user by stubbing the
current_user method on any instance of the ProjectsController. We use this approach because the instance of the ProjectsController will be automatically created for us.
But what's really nice is that we can use our example to specify the code we wish we had. So far our
User model doesn't have a method
member_of?. But it would read nicely, so I decided that a user should have this method. Let's make this example work:
class ProjectsController < ApplicationController def show @project = Project.find params[:id] current_user.member_of? @project end end
Great, now it works! But in fact this doesn't change the action's behavior. What we want is to redirect users to another page if they aren't team members of the project. So let's add an example for this:
describe ProjectsController do it "should redirect non-team members to their projects overview" do user = double member_of?: false ProjectController.any_instance.stub current_user: user response.should_redirect_to projects_path get :show, project_id: project.id end end
To make this example work we could also write the following:
class ProjectsController < ApplicationController def show @project = Project.find params[:id] current_user.member_of? @project redirect_to projects_path end end
But this would redirect all users to the projects path. So one more example:
But before we do that let's clean up our examples first. Like application code you should also refactor your examples to avoid repetition and keep them clean. Consider refactoring your examples everytime you made them pass, but refrain from refactoring when there are still failing examples, this will make everything much worse. Trust me, I've been there.
Let's move all our duplication into a before each block. This will be executed before each example.
describe ProjectsController do it "should show the project to team members" do user = double member_of?: true ProjectController.any_instance.stub current_user: user response.should_render_template :show get :show, project_id: project.id end end
When we change our application code now
class ProjectsController < ApplicationController def show @project = Project.find params[:id] redirect_to projects_path unless current_user.member_of? @project end end
all examples work. Great!
So we've got a perfectly readable controller action by specifying code that doesn't exist yet. I like this method of designing a component's interface, because usually it's much easier to specify simple code than complicated code. So when you stick to this pattern, your code will become more readable and more structured automatically.
But where do we go from here? Well, we've specified everything we need from our controller, so let's run our scenario again.
It fails telling us that there is no method
User. So our scenarios are the always safe-guards that remind us to implement everything that's still missing to make our new feature work.
So let's implement this method now. A user should be a member of a project when it has got a project membership. So we define one example for our
describe User do context "verifying a membership" do it "should query a membership" do memberships = double user = User.new project = mock_model Project user.stub memberships: memberships memberships.should_receive(:where).with project_id: project.id user.member_of? project end end end
So we want to check for a membership with the project's id in the database. Let's make this example work:
class User < ActiveRecord::Base def member_of? project memberships.where(project_id: project.id) end end
But now this method will return a database relation instead of true or false. So we need a second example:
describe User do context "verifying a membership" do it "should be a member if a membership exists" do user = User.new project = mock_model Project user.stub_chain(:memberships, :where, :exists?).and_return true user.member_of?(project).should be_true end end end
The easiest way to make this work is to return true in our method:
class User < ActiveRecord::Base def member_of? project memberships.where(project_id: project.id) true end end
But that's not what we want. Let's add one more example:
describe User do context "verifying a membership" do it "shouldn't be a member if no membership exists" do user = User.new project = mock_model Project user.stub_chain(:memberships, :where, :exists?).and_return false user.member_of?(project).should be_false end end end
Now our implementation will look like this:
class User < ActiveRecord::Base def member_of? project memberships.where(project_id: project.id).exists? end end
Let's run Cucumber again. It tells us that there's no method
memberships for our
User. So the only thing left to do now is to create the membership relation between the user and the project. With Rails we can create this relationship on the command line:
rails generate model Membership user:belongs_to project:belongs_to
Now we only need to migrate the database and also propagate the changes to the test database:
rake db:migrate && rake db:test:prepare
And we tell the user that it has got membeships now:
class User has_many :memberships end
If we run our feature now, it succeeds.
The nice thing about this outside-in approach is, that you define the methods you need first and take care of their implementation later. Without this approach we would have probably started by creating the
Membership model and then we would have written a controller action. But then, the controller action would have probably looked like this:
def show @project = Project.find params[:id] unless @project.memberships.where(user_id: current_user.id).exists? redirect_to projects_path end end
This action is much less readable than the one before. We deal with an abstract
Membership term instead of just asking if a user is member of a project. And we increased complexity by having a long method chain with a database query in our controller action.
But what's even worse: This code is not reusable, whereas our
member_of? method can be used many times throughout the application. Of course you could also refactor this controller action and create the
member_of? method afterwards. But this would be an additional step and these get forgotten quite often. With our outside-in approach this code just evolved naturally out of our process.
However, some of you told me that they don't like running the integration tests to verify that their stubbed behavior still works, because it slows them down.
I tend to run only unit specs and the Cucumber feature I’m currently working on on my local machine. Once I’ve completed a feature I push it to GitHub and the Codeship will pick it up and run the entire feature suite for me. If something broke, it would inform me. So I can keep working while the Codeship checks my current code.
I’m a big fan of having integration tests check the entire system's behavior before I ship something. However, if you just want to verify that your mocked behavior actually works while you're developing a feature, this feedback cycle can be cumbersome. Especially when you’ve got an extensive integration test suite, getting feedback from your continuous integration system can take 20 minutes or more.
Fortunately there are tools that verify your mocked behavior faster. But that's enough for today. Thanks to Rafael and Cezar who suggested "bogus" to solve this problem. We'll take a look at it next week.
See you next Testing Tuesday, and just remember one thing: Always, really always stay shipping!
Stay up to date
We'll never share your email address and you can opt out at any time, we promise.