Anonymous Functions in JavaScript are Evil

Anonymous functions in JavaScript can cause a lot of headache and pain. If you want to write testable JavaScript, you will spend days scratching your head to find a way to assert logic that resides in an anonymous function. The problem is that we have no control over what happens inside the anonymous function, unless we stub the sucker out and choose whatever happens after the function is called. But that’s not really a smooth way to do it, is it? There are ways to get around this issue, but some of them are messy in my opinion. There isn’t really a perfect way to do it, but I will show you two different ways that I know are possible.

Consider studentsController.js which has a function getStudents() that calls an injected service studentService:

var getStudents = function() {
    var _this = this;
    this.studentService.getAll(function (data) {
	_this.handle(data);
    });
};

We are testing the getStudents() function, how do we assert that the handle() function was called or that the data was updated correctly? It’s happening in an anonymous function call.

“Testing hooks”

One way to do it is to use so called “testing hooks”. This is in my opinion a messy way, since you have to insert code into the logic just to handle testing. What is possible however, is to make a post script that removes all testing hooks prior to production release. That’s still extra work to do though. You attach a hook onto the studentService.getAll() function like the following (line 6):

var getStudents = function() {
    var _this = this;
    this.studentService.getAll(function (data) {
	_this.handle(data);
    });
    arguments.callee.anonymous = this.studentService.getAll;
};

Now, in your test you need to call getStudents() once. Then you can control the data that goes inside the anonymous function like so:

getStudents.anonymous(data)

Stubbing Your Way Down the Dependency Rabbit Hole

Another possible way is to literally stub your way down the dependency hierarchy and spy on dependencies that get called. This is a painful way. You need an API to create fake objects, assume that we use Jasmine. In this case, you mock an object – http – in your test that gets called by studentService.getAll(), like so:

var httpObject = jasmine.createSpyObj('http', ['get']);
httpObject.get.andReturn("data");

Then you call getStudents() once and expect that the mock was called:

expect(getStudents()).toBe("data");

There are probably other ways to get around this, but I have found that option one is the simplest way. I’m also quite comfortable with it, as long as those hooks get removed pre-production. 🙂

Advertisements