How Spy/Stub or fake a functions
Testing functions attached to objects, other than a class instance, constitutes an intimidating edge case from first sight. Such objects range from object literals to modules. This blog explores some test doubles techniques to shine a light on such cases.
For context, the difference between a function and a method, is that a method is a function encapsulated into a class.
In this article we will talk about:
- Key difference between a spy, stub, and a fake
- When it makes sense a spy over a stub
Even though this blog post was designed to offer complementary materials to those who bought my Testing
nodejs
Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book.
Show me the code
var fs = require('fs');
module.exports.removeUserPhoto = function(req, res, next){
let filepath = `/path/to/photos/${req.params.photoId}.jpg`;
fs.unlink(filepath, (error) => {
if(error) return next(error);
return res.status(200).json({
message: `Your photo is removed - Photo ID was ${req.params.photoId}`
});
});
}
Example: A simple controller that takes a PhotoID and deletes files associated to it
What can possibly go wrong?
Some challenges when mocking chained functions:
- Stubbing a method, while keeping original callback behavior intact
Show me the tests
From the How to mock chained functions article, there are three relevant to the current context avenues we leverage for our mocking strategy.
let outputMock = { ... };
sinon.stub(obj, 'func').returns(outputMock);
sinon.stub(obj, 'func').callsFake(function fake(){ return outputMock; })
let func = sinon.spy(function fake(){ return outputMock; });
We can put those approaches to test in the following test case
var sinon = require('sinon');
var assert = require('chai').assert;
// Somewhere in your code.
it('#fs:unlink removes a file', function () {
this.fs = require('fs');
var func = function(fn){ return fn.apply(this, arguments); };//mocked behaviour
//Spy + Stubbing fs.unlink function, to avoid a real file removal
var unlink = sinon.stub(this.fs, "unlink", func);
assert(this.fs.unlink.called, "#unlink() has been called");
unlink.restore(); //restoring default function
});
Conclusion
In this article, we established the difference between stub/spy and fake concepts, how they work in concert to deliver effective test doubles, and how to leverage their drop-in-replacement capabilities when testing functions.
Testing tends to be more of art, than a science, practice makes perfect. There are additional complimentary materials in the “Testing nodejs
applications” book.
References
- Testing
nodejs
Applications book - Evan Hahn in “How do I Jasmine“ – has pretty good examples that leverages spies along the way.