I'm trying to learn how to do JS unit testing using Sinon, Mocha, and Chai. Right now, I want to confirm that one of my functions actually makes an api request (so I need to mock my function). I believe that I've found relevant mocking code in the Sinon docs (it's code for testing Ajax), however, I can't make any sense of what's going on (the documentation is very sparse). Can anyone explain each of the 3 lines of code in the it function (directly below)?
it("makes a GET request for todo items", function () {
sinon.replace(jQuery, "ajax", sinon.fake());
getTodos(42, sinon.fake());
assert(jQuery.ajax.calledWithMatch({ url: "/todo/42/items" }));
For reference, the function being mocked is directly below:
function getTodos(listId, callback) {
jQuery.ajax({
url: "/todo/" + listId + "/items",
success: function (data) {
// Node-style CPS: callback(err, data)
callback(null, data);
},
});
}
Also, here is a link to the documentation: https://sinonjs.org/
Let's go step by step:
Sinon creates a new fake function and replaces jQuery.ajax function with it.
Any other calls to jQuery.ajax
will invoke the fake. By default, the fake function returns undefined
, you can control that as well; but it is not needed here(since we will assert if it's called with certain arguments or not). The fake function has the same methods as spies, so a fake function will record information regarding its calls, like spies. (whether it is called, with which arguments etc.)
getTodos
is the function that is tested. This calls it with 42 as first argument and a new fake function as second argument(callback argument).
Probably, a fake is passed to prevent side effects of using an actual callback. one can also put a empty function instead like
getTodos(42, () => {});
The callback function does not matter for this test. It just needs to be discarded/mocked.
The assertion checks for the expression jQuery.ajax.calledWithMatch({ url: "/todo/42/items" })
to be true. Remember, jQuery.ajax
was replaced with a fake function. So, the fake function's calledWithMatch
method is called. According to the docs of calledWithMatch(in spies section):
spy.calledWithMatch(arg1, arg2, ...);
Returns true if spy was called with matching arguments (and possibly others).
This behaves the same as spy.calledWith(sinon.match(arg1), sinon.match(arg2), ...).
so if the fake function is called with arguments(only one provided here) that are matching, it returns true. "Matching" behaviour is defined via matchers. Here object matcher is used(sinon.match(object)), which matches if
sinon.match(object);
Requires the value to be not null or undefined and have at least the same properties as expectation.
This supports nested matchers.
So, this object is passed to jQuery.ajax in getTodo:
{
url: "/todo/42/items",
success: function (data) {
callback(null, data);
},
}
which matches
{ url: "/todo/42/items" }
according to the object matcher definition. So, this matcher checks if jQuery.ajax method is called with an object that includes a url property with value "/todo/42/items"
.
jQuery.ajax
since we don't want to make actual requests, just want to check if it's called.getTodos
, which is our test subject, with 42 and a fake callback arguments.jQuery.ajax
to be called with an object that has url property with value "/todo/42/items".