· Ilya Kaminsky · 3 min read
Exploring Jasmine’s Custom Asymmetric Equality Tester
There’s a hidden gem in the Jasmine testing framework called asymmetricMatch. Since I could not find it being mentioned anywhere on the…
There’s a hidden gem in the Jasmine testing framework called asymmetricMatch. Since I could not find it being mentioned anywhere on the blogosphere, I have decided that it deserves a dedicated post of its own.
The asymmetric equality tester allows us to test any custom expectation at a later time. To illustrate this point, I have combined three test scenarios in a single project on GitHub. Feel free to clone the entire repository and to follow along.
Angular-Foundation Modal Resolver
When opening modals with Angular-Foundation, the docs specify that each resolved item needs to be defined using a function that returns a value. In order to avoid holes in the test coverage report, I used to resort to mocking the $modalProvider in its entirety. However, that would result in having huge spec files that would needlessly test a big chunk of the Angular-Foundation module.
With Jasmine’s asymmeticMatch function, it is easy to only test the code that matters most. Here’s how:
Notice that with this setup, the `valueTester` is not being called until `resolve.value()` is invoked.
some-controller.js
$modal.open({
resolve: {
value: function() {
return _this.value;
}
}
});some-controller.spec.js
describe('$modal.open()', function() {
it('should resolve the value', function() {
var valueTester = {
asymmetricMatch: function(value) {
return value() === controller.value;
}
};
expect($modal.open)
.toHaveBeenCalledWith(jasmine.objectContaining({
resolve: {value: valueTester}
}));
});
});Angular UI-Router State Resolver
Similarly to the modal resolver above, the Angular UI-Router also resolves values asynchronously via a resolve function. The added complexity here is that it all happens within the configuration block, meaning that we also have to inject a mock provider. In fact, Nikas Praninskas wrote an excellent tutorial on how to unit test the UI-Router resolve method. By using the asymmetric equality tester, I argue that it’s possible to achieve the same result with a lot less boilerplate code. Take a look at the following Gist:
We are not limited to a hard-coded `‘user_id’` string. By injecting `ngResource`, it is possible to test the resource resolution of the desired state or view.
router-configuration.js
var someState = {
resolve: {
user: function() {
return 'user_id';
}
}
};
$stateProvider.state(someState);router-configuration.spec.js
beforeEach(module('ui.router', function($stateProvider) {
$state = $stateProvider;
spyOn($state, 'state');
}));
beforeEach(function() {
module('some-module');
inject();
})
describe('some state', function() {
it('should resolve the user', function() {
var userTester = {
asymmetricMatch: function(user) {
return user() === 'user_id';
}
};
expect($state.state)
.toHaveBeenCalledWith(jasmine.objectContaining({
resolve: {user: userTester}
}));
});
});ngRoute Route Resolver
Since the ngRoute module closely resembles the previous example, I have included it below as well. Here’s what testing its resolve function would look like when combined with asymmetricMatch:
As with the previous example, here too we can spy on other dependencies just as well.
route-configuration.js
$routeProvider
.when('/', {
resolve: {
user: function() {
return 'user_id';
}
}
});route-configuration.spec.js
beforeEach(module('ngRoute', function($routeProvider) {
$route = $routeProvider;
spyOn($route, 'when');
}));
beforeEach(function() {
module('some-module');
inject();
})
describe('some route', function() {
it('should resolve the user', function() {
var userTester = {
asymmetricMatch: function(user) {
return user() === 'user_id';
}
};
expect($route.when)
.toHaveBeenCalledWith('/', jasmine.objectContaining({
resolve: {user: userTester}
}));
});
});Have you used Jasmine’s asymmetricMatch in other scenarios? Please share your experience in the comments section below. Pull requests are welcome.



