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:
Spec frameworks for other languages:
Java – why don't you try running RSpec using JRuby?
Python – Should-DSL gives you RSpec-like matchers. Mock lets you replace parts of your system with mock objects.
PHP – There's phpspec and spectrum for RSpec-like specifications and Phake for mocking.
NodeJS – Choose one of mocha, Jasmine and nodespec. For mocking you can use Sinon.JS, node-gently and nodemock.
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:
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.
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:
Spec frameworks for other languages:
Python – Should-DSL gives you RSpec-like matchers. Mock lets you replace parts of your system with mock objects.
PHP – There's phpspec and spectrum for RSpec-like specifications and Phake for mocking.
NodeJS – Choose one of mocha, Jasmine and nodespec. For mocking you can use Sinon.JS, node-gently and nodemock