RSpec - Test Doubles



In this chapter, we will discuss RSpec Doubles, also known as RSpec Mocks. A Double is an object which can “stand in” for another object. You’re probably wondering what that means exactly and why you’d need one.

Let’s say you are building an application for a school and you have a class representing a classroom of students and another class for students, that is you have a Classroom class and a Student class. You need to write the code for one of the classes first, so let’s say that, start with the Classroom class −

class ClassRoom 
   def initialize(students) 
      @students = students 
   end 
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
end

This is a simple class, it has one method list_student_names, which returns a comma delimited string of student names. Now, we want to create tests for this class but how do we do that if we haven’t created the Student class yet? We need a test Double.

Also, if we have a “dummy” class that behaves like a Student object then our ClassRoom tests will not depend on the Student class. We call this test isolation.

If our ClassRoom tests don’t rely on any other classes, then when a test fails, we can know immediately that there is a bug in our ClassRoom class and not some other class. Keep in mind that, in the real world, you may be building a class that needs to interact with another class written by someone else.

This is where RSpec Doubles (mocks) become useful. Our list_student_names method calls the name method on each Student object in its @students member variable. Therefore, we need a Double which implements a name method.

Here is the code for ClassRoom along with an RSpec Example (test), yet notice that there is no Student class defined −

class ClassRoom 
   def initialize(students) 
      @students = students 
   end
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
end

describe ClassRoom do 
   it 'the list_student_names method should work correctly' do 
      student1 = double('student') 
      student2 = double('student') 
      
      allow(student1).to receive(:name) { 'John Smith'} 
      allow(student2).to receive(:name) { 'Jill Smith'} 
      
      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith') 
   end 
end

When the above code is executed, it will produce the following output. The elapsed time may be slightly different on your computer −

. 
Finished in 0.01 seconds (files took 0.11201 seconds to load) 
1 example, 0 failures

As you can see, using a test double allows you to test your code even when it relies on a class that is undefined or unavailable. Also, this means that when there is a test failure, you can tell right away that it’s because of an issue in your class and not a class written by someone else.

Advertisements