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.