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)
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.
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
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.
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.
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 ?