· 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…

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}
      }));
  });
});

View the original Gist

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}
      }));
  });
});

View the original Gist

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}
      }));
  });
});

View the original Gist

Have you used Jasmine’s asymmetricMatch in other scenarios? Please share your experience in the comments section below. Pull requests are welcome.

    Share:
    Back to Blog

    Related Posts

    View All Posts »
    Deploying AI to the Cloud

    Deploying AI to the Cloud

    This is a tale of the hurdles I had to overcome in my pursuit of deploying an artificial intelligence based program to the cloud…