← Back to Upcase

Stubs, Mocks, Spies, and Fakes

(Upcase ) #1
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

(Andy Waite) #2

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

(Andy Waite) #3

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

(Joe Ferris) #4

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.

(Sean Huber) #5

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)

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!

(Joe Ferris) #6

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.

(YangBo) #7

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?

(Aaron Mc Adam) #8

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)

(Anthony) #9

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?

(Ben Orenstein) #10

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.

(Dain Miller) #11
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

(Anthony) #12

Hi Ben,

Oh I see. So a ‘collaborator’ that’s the class, who’s method is called by the class under test?



(Ben Orenstein) #13

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?

(Anthony) #14

Yeah, that makes sense. Thanks Ben.

(Jonny Adshead) #15

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.

(David) #16

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.

(Ben Orenstein) #17

Yeah, this is Rails being annoyingly class-focused.

In this case I’d probably use factory_girl's build_stubbed method.

(David) #18

Thanks! I’m guessing the classes used in the video aren’t ActiveRecord models?

(Ben Orenstein) #19

That’s right.

(Nikola Novakovic) #20

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 ?