Easy testing of code involving native methods in JavaScript
When using ‘use strict;’ in your scripts you’ll find that you are no longer allowed to overwrite native methods like FileReader() so how do you test that these methods are being called with the appropriate parameters? Lets start with a typical function call involving FileReader() and then modify it to make it easier to test.
function importFile(file) {
var reader = new FileReader();
reader.onload = function(e) {
processData(e.target.result);
};
reader.readAsText(file);
}
Pre ‘use strict’; days you could simply stub out the global FileReader() but since that’s no longer an option we need to get a little creative with our code structure. First thing we’re going to do is create a FileReader instance generator function.
function importFile(file) {
var reader = generateFileReader();
reader.onload = function(e) {
processData(e.target.result);
};
reader.readAsText(file);
}
function generateFileReader() {
return new FileReader();
}
Then we’ll move the onload callback to a named function.
function importFile(file) {
var reader = generateFileReader();
reader.onload = _readerOnloadHandler;
reader.readAsText(file);
}
function generateFileReader() {
return new FileReader();
}
function _readerOnloadHandler(e) {
processData(e.target.result);
}
Now you can test the importFile function and its parts by stubbing out the generateFileReader function to return a basic reader stub and not have to worry about the native method. In the following example I’m using two simple stubbing methods to generate stub functions and methods.
it('parses files', function() {
// Set up the stubs.
var processStub = stubMethod('processData');
// The second parameter of the stubMethod is what generateFileReader
// will return when it's called.
var reader = stubMethod('generateFileReader', {
onload: null,
readAsText: stubFunction();
});
// Call the public method.
importFile('/path/to/file');
// Make assertions
assert.equal(reader.calledOnce(), true);
assert.equal(reader.readAsText.calledOnce(), true);
assert.equal(reader.readAsText.lastArguments()[0], '/path/to/file');
// Call the callback.
reader.onload({ target: { result: 'file data' }});
// Make assertions
assert.equal(processStub.calledOnce(), true);
assert.equal(processStub.lastArguments()[0], 'file data');
});
Splitting up the code in this way makes unit testing possible because you are essentially wrapping the native function call in a function which are you are able to stub out. Happy testing!