Stubs, Mocks, Spies, and Fakes

Joe and Gabe review the four types of test doubles: stubs, mocks, spies and fakes. Learn what the key differences are between each type, as well as when you'd want to consider using them. See examples written using rspec-mocks, as well as an examp...
This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/stubs-mocks-spies-and-fakes

Very useful and practical advice. Definitely one of my favourite episodes.

Does the Signup.save method in the examples violate command-query separation? (It returns a boolean but also modifies @user).

Yes, the save method from ActiveRecord is both a command and query method. There isnā€™t a rule this is violating, but such methods are a little more difficult to implement, test, and understand, so itā€™s best to avoid combining commands/queries into a single method when there isnā€™t a really good reason to do so.

The save method is a tricky one, because it does something that might fail. If save were just a command method, youā€™d need to follow up by invoking a different query method (valid? or persisted?). The authors of ActiveRecord decided the convenience of a single save method was worth the complexity of a combined query/command method.

Do you guys generally try to avoid stubbing methods when you create doubles? For example, in the first set of test cases, the line

allow(clone).to receive(:solution).and_return(solution)

could be removed by stubbing solution when the clone double is created e.g.

double("clone", solution: solution)

Then when you use spies it ends up being the same number of lines as the ā€œstubā€ solution, but all the expectations are at the end which is nice.

describe "#solution" do
  solution = double("solution")
  clone = double("clone", solution: solution)
  participation = Participation.new(clone: clone)

  result = participation.solution

  expect(clone).to have_received(:solution)
  expect(result).to eq(solution)
end

Just wondering if itā€™s more of a best practice type convention that I havenā€™t picked up on yet. Iā€™ve been loving this series and looking forward to new episodes every week, thank you!

I stuck with the allow syntax in order to be consistent during the video. If Iā€™m stubbing out a method that takes no arguments and returns a meaningful value (like an attribute), Iā€™ll use the double shorthand.

Do you mean you would like more readable if used allow for stubbing?
double just double.
if want to stubbing attributes or methods, allow is good chose?

Another option I use a lot is the spy method (introduced in RSpec 3.1) which means you donā€™t have to repeat what the name of the method youā€™re mocking:

clone = spy("clone")
expect(clone).to have_received(:solution)
1 Like

Something Iā€™m still not fully grasping about using stubs or mocks. In most examples, a stub - or mock - is created that receives some method and returns some value. Then this stub is passed to an object, and then we test that this object received stubbed method or is equal to the passed value. Arenā€™t we just testing our setup?

No. Youā€™re testing that the system under test is sending the right messages to its collaborators.

You should only be stubbing collaborators, not the class youā€™re testing.

1 Like
You should only be stubbing collaborators, not the class you're testing.

That sentence finally allowed me to understand stubbing and mocking objects. I never quite grasped it, and now it makes perfect sense. (The video was a bit confusing for me because I didnā€™t understand the why before we dove into implementation details).

Thanks Ben

1 Like

Hi Ben,

Oh I see. So a ā€˜collaboratorā€™ thatā€™s the class, whoā€™s method is called by the class under test?

greetings,

Anthony

If Iā€™m testing any class A, I refer to any other class that A uses as a collaborator.

When Iā€™m testing A, I want to do so in isolation from its collaborators. One way to achieve this is by stubbing Aā€™s collaborators.

Make sense?

Yeah, that makes sense. Thanks Ben.

It took me almost 1/2 the video to figure out that they were only stubbing collaborators. I wish I had read the comments before. Thanks Ben for clarifying.

1 Like

Iā€™ve been trying to implement something similar to the videoā€™s first example in one of my own tests. Setup looks like this:

route = double("route")
flight = Flight.new(route: route)

However, this raises the following exception:

[3] pry(#<RSpec::ExampleGroups::Flight::Duration>)> flight = Flight.new(route: route) 
ActiveRecord::AssociationTypeMismatch: Route(#70156849968700) expected, got RSpec::Mocks::Double(#70156836399260)

What Iā€™m doing seems to be more or less the same as what is going on in the video with the clone double and Participation.new(clone: clone). Is it possible that Iā€™m missing some option that would allow me to use a double this way? I did some googling and found some people mentioning the mock_model method, which is now deprecated, but that wasnā€™t used in the video to begin with.

Yeah, this is Rails being annoyingly class-focused.

In this case Iā€™d probably use factory_girlā€™s build_stubbed method.

2 Likes

Thanks! Iā€™m guessing the classes used in the video arenā€™t ActiveRecord models?

Thatā€™s right.

The thing that @benorenstein mentioned about stubbing out only collaborators helped so much.

I was wondering in which use case you would prefer using something like Factory Girl ( or fixtures ) instead of mocks/stubs ?