September 2014
Yes. I tried to clarify that near the beginning of the article. (Did you read an older version?) What you call "fake", I call "stub". (I used to also say "fake", which I learned from Dave Astels. I now use the London vocabulary.)
In the Javascript world (as in Java with Mockito), they use "spies" for both spying and stubbing.
September 2014
Indeed. This does, however, lead me to another question. Consider a controller that queries, then acts: it asks for all the customers with pending orders, then merges that with a view template. The controllers seems responsible for (at least) two things: (1) merging a valid set of customers with the view template and (2) choosing customers with pending orders (as opposed to some other set of customers). My question: since we stub the query, how do *you* become confident that the controller asks the model for the correct set of customers without setting an expectation on `Customers.where(order_status: :pending)`? This has always bothered me.
2 replies
September 2014
▶ jbrains
Maybe the answer is that although Steve and Nat find the simple rule to "Allow queries, expect commands" helpful, they also say that the distinction between allowances and expectations isn’t rigid, and so maybe there is some duality at play: maybe what they mean is that a call that the object under test makes on a collaborator can be treated as an allowance in one test and as an expectation in the next one.
Maybe there is support for this hypothesis in the following excerpts from chapter 24 of GOOS:
"jMock insists that all expectations are met during a test, but allowances may be matched or not. The point of the distinction is to highlight what matters in a particular test. Expectations describe the interactions that are essential to the protocol we’re testing: if we send this message to the object, we expect to see it send this other message to this neighbor.
Allowances support the interaction we’re testing. We often use them as stubs to feed values into the object, to get the object into the right state for the behavior we want to test. We also use them to ignore other interactions that aren’t relevant to the current test. For example, in “Repurposing sniperBidding()” we have a test that includes:
ignoring(auction);
allowing(sniperListener).sniperStateChanged(with(aSniperThatIs(BIDDING)));
then(sniperState.is("bidding"));
The ignoring() clause says that, in this test, we don’t care about messages sent to the auction; they will be covered in other tests. The allowing() clause matches any call to sniperStateChanged() with a Sniper that is currently bidding, but doesn’t insist that such a call happens. In this test, we use the allowance to record what the Sniper has told us about its state. The method aSniperThatIs() returns a Matcher that checks only the SniperState when given a SniperSnapshot.
In other tests we attach “action” clauses to allowances, so that the call will return a value or throw an exception. For example, we might have an allowance that stubs the catalog to return a price that will be returned for use later in the test:
allowing(catalog).getPriceForItem(item); will(returnValue(74));
...
Like all “power tools,” ignoring() should be used with care. A chain of ignored objects might suggest that the functionality ought to be pulled out into a new collaborator. As programmers, we must also make sure that ignored features are tested somewhere, and that there are higher-level tests to make sure everything works together. In practice, we usually introduce ignoring() only when writing specialized tests after the basics are in place."
1 reply
October 2014
▶ pschwarz
I do this precisely. When I want to check that my controller handles "query returned an empty result", I stub the query, but expect the corresponding action. When I want to check that my controller passes the correct parameters to the query, I set an expectation on the query. It works, but I wonder whether others do things differently.
Certainly, I prefer not to care about the details of a query, so that whatever expectations I set become as stable as possible. That said, if the details of a query change frequently, that points to a missing abstraction somewhere, I think.
October 2014
▶ jbrains