Test Spies, Stubs and Mocks - Part 1.5
While presenting the core Sinon interface yesterday i unintentionally left out parts of the API. To make up for the mistake, here are the missing pieces.
Spies
In the original post I wrote about the data spies collect in arrays as well as the methods spies expose to interact with the recorded call data. However, you might have recognized a gap in the interface - you can either match any one call, or all of them. If you need more fine-grained control, spies have a getCall(num) method which retrieves a specific call, which in turn supports a similar interface to that of the spies themselves.
Behavior verification for individual calls
Given the original example:
sinon.spy(jQuery, "ajax");
jQuery.getJSON("/some/data");
You can retrieve the first (and in this case, only) call with:
var call = jQuery.ajax.getCall(0);
Then you can match against its recorded data using:
-
call.calledWith(arg1, arg2, ...)
Pass in any number of arguments, and the method returnstrueif the call received the specified arguments. The list of arguments does not need to be exhaustive - you can provide the first argument, and the method will returntrueif the call received this argument as its first (possibly with additional arguments). -
call.calledWithExactly(arg1, arg2, ...)
If you need to know if the call received certain arguments, and nothing but those certain arguments, this method is the strict variation ofcalledWith(arg1, arg2, ...). -
returned(returnValue)
Returnstrueif the call returned the specified value. -
threw(exceptionOrType)
If no arguments are passed, the method returnstrueif the call threw an exception. If a string argument is passed, it returnstrueif the call threw an exception of the provided type, such as"TypeError". Finally, if an exception object is passed,trueis returned if the call threw this exact exception object (not likely to be used much, but there for completeness). -
calledOn(thisObj)
Returnstrueif the call was made with the specified object asthis.
Basically, each call support the same exact interface as the stub, save for the "always" variation. When called on a specific call, the methods naturally match only that one call.
These methods are used extensively in the implementation of spies, but I'd be careful with using them extensively in tests. Targeting specific calls like this may cause your tests to be unnecessarily implementation specific, thus brittle.
Call order
I also left out two methods on spies: calledBefore and calledAfter. Both of these can come in handy in certain situation, but are also subject to the same reservation I just mentioned: exaggerated use will cause too implementation specific tests.
To keep with the jQuery examples, the following test could be imagined to be part of jQuery's test case for the ajax method.
"test using XMLHttpRequest open should be called before send": function () {
var open = sinon.stub(XMLHttpRequest.prototype, "open");
var send = sinon.stub(XMLHttpRequest.prototype, "send");
jQuery.ajax("/some/url");
assert(open.calledBefore(send));
assert(send.calledAfter(open));
}
The two asserts in the above test case are basically testing the same, which one to use is a matter of preference.
Stubs
I left out a little detail from the stub interface as well. As you may have noticed, stubs, mocks and spies all are function-centric in Sinon. From my own experience, I very rarely need to fake entire objects. A single test rarely need to fake out more than a handful of functions at a time, so focusing on functions makes sense.
For those rare cases where you really want to stub all the methods of an object, you can use the sinon.stub() function with only an object as first argument. Doing so instructs Sinon to stub out all the methods. If you need you can fine-tune the stubs later, as seen in the following example:
sinon.stub(XMLHttpRequest.prototype);
XMLHttpRequest.prototype.getResponseHeader.returns("");
jQuery.ajax("/some/url");
// ...
Here, Sinon stubs all the methods on XMLHttpRequest.prototype. This means they're all stub functions now, allowing you call the stub and spy methods on them directly to further tweak their behavior.
There's no equivalent to this for mocks. If you really want to create mock expectations on all the methods of an object in a test you might want to reconsider your test design - chances are it's doing more than it should. I have no plans to implement a way to mock and set expectations on more than a single method at a time as I don't see a viable use case for it.
Note that you can mix and match as well. If you want to silence an object and set an expectation on one or a few of the methods, you can do that:
"test should always pass argument to send": function () {
sinon.stub(XMLHttpRequest.prototype);
var mock = sinon.mock(XMLHttpRequest.prototype);
// If no argument is passed to send on GET requests, FF <= v3.0 will
// throw an exception
mock.expects("send").once().withArgs(null);
jQuery.ajax("/some/url");
mock.verify();
}
The above test silences all the native XMLHttpRequest methods, and then overrides the stub created for send with a mock expectation that expects an argument to be passed.
That's about it. In the next post, which is the real second post, I'll discuss integration with xUnit style testing frameworks.
Comments
Andrew J. Leer
(http://www.andrew-leer.com/)
3. June, 21:20
I only ask because there seem to be many vocabularies for this.
Christian
(http://cjohansen.no/en)
3. June, 22:37
The exception I mentioned is with spies. I feel that Sinon implements spies as described by the above book, but in Sinon a spy doesn't need to be a fake object. This is merely a result of JavaScript's nature, which allows spies to wrap real methods real easily. Currently, spies are mostly used in the implementation of Sinon, and you'll most likely interact with them through stubs (which inherit the interface). A Sinon stub with no "canned answers" should fit Meszaros spies pretty well.
As for Mocks, I'm with Fowler, and I think a lot of people mix them up with "anything which is not the real thing", mostly stubs. In Sinon, a mock is the only object which can have pre-programmed expectations.
What are your thoughts?
Christian
(http://cjohansen.no/en)
3. June, 22:45
In case you're not familiar with the references, here's the short definitions I lean on:
Spy: A function which records any interaction with it - arguments, this value, return value and exceptions, for each call.
Stub: A function which has pre-programmed behavior - i.e. it's return value is set upfront, and it can be instructed to throw exceptions. I'm also working on some kind of an API to instruct it to call callbacks automatically.
Mock: A function with possibly pre-programmed behavior and most importantly - pre-programmed expectations. A single mock function can have several mock expectations, e.g. "should be called once with arg a", "should be called once with object b as this" and so on.
Andrew J. Leer
(http://www.andrew-leer.com/)
4. June, 14:52
I found the following image about unit testing (which is inaccurate...) but seems as though we could fangle it in some way to make it clearer as to when the different types of fake objects.
http://www.hamletdarcy.com/files/2007/MocksAreNotSpies.jpg
Christian
(http://cjohansen.no/en)
6. June, 00:54
school grants
(http://www.topschoolgrants.org)
9. June, 09:31
Andrew J. Leer
(http://www.andrew-leer.com/)
9. June, 15:43
In particular I'd like to learn in what situations to use the various types of fake objects.
Christian
(http://cjohansen.no/en)
9. June, 20:39
Also, I'd like to plug <a href="http://www.amazon.com/dp/0321683919">my own upcoming book</a>, which deals with these concepts in detail within the context of JavaScript. It's also what prompted me to write Sinon ;)
Andrew J. Leer
(http://www.andrew-leer.com/)
9. June, 22:55
Christian
(http://cjohansen.no/en)
9. June, 23:58
Comments are closed