At my day job, we’ve recently embarked on a project to improve our test coverage for our AngularJS based mobile site, which currently sits at a measly 40%. So I’ve been writing lots of unit tests lately and keep trying to find ways to make this tedious process a little bit easier and more enjoyable. A few days ago I discovered a new way to mock dependencies in AngularJS which makes unit tests leaner and simpler to implement.
One of the golden rules of unit testing is that to properly test a component, it should be kept in complete isolation from any other part of the application. This means that you should focus on testing only the code of that single component. The code of any referenced components (i.e. dependencies) should either be ignored (if you do not expect a return) or mocked (if you need to continue processing the result). Angular’s dependency injection is one of the core concepts behind the framework, so if you’ve been writing nice, modular applications, you’re going to have lots of dependencies spread throughout your code – which is a good thing, because it allows for proper unit testing to take place by testing all these components individually.
Angular gives you a lot options when choosing how to mock your dependencies. First of all, mocks of some of Angular’s most commonly used built-in components (like $http ) are provided by the creatively named Angular-Mocks module and can be used out-of-the-box in your tests. For any of your custom components like services and constants, you can use spies to mock specific functions using the callFake() method. You can also use the $provide function to inject mocked versions of a dependency, or just inject the mocked dependency directly when creating a component e.g. when using $controller to create a controller.
There’s nothing wrong with any of these methods to mock dependencies in Angular, they exist to be used and all have their specific use cases. But using these methods individually in your test code is time-consuming, makes it difficult to keep track of what has been mocked already and what has not, and also adds bloat to the whole test code. These problem have led me to start using my new favourite method to mock dependencies in AngularJS unit tests: creating and injecting mock modules.
The idea behind this is the same as with the other methods, you mock your dependencies so that your test code does not depend on their implementation. But by placing all the mocks in a module, you can easily reuse the same mocks for all your tests, making it much more scalable and cleaner to implement. Let’s take a look at the example below written with Jasmine 2.3.
Say you have the below app setup:
var app = angular.module('App'); app.service('ComplexHelper', function () { this.performCalculations = function () { /* Performs complex calculations */ return data; }; return this; }); app.controller('AppController', function ('ComplexHelper') { var ctrl = this; ctrl.results = []; ctrl.addResult = function () { var result = ComplexHelper.performCalculations(); ctrl.results.push(result); }; });
So you’ve got a controller which calls the ComplexHelper service’s performCalculations function, which performs some complex calculations (big surprise!). Since we don’t want our controller test to depend on the complex code in the ComplexHelper service, we need to mock the dependency. One way that we could do that is by using the aforementioned spies:
describe('AppController', function () { beforeEach(module('App')); beforeEach(inject(function ($controller, _ComplexHelper_) { appCtrl = $controller('AppController'); ComplexHelper = _ComplexHelper_; })); it('should add get a complex result and add it to the array of results', function () { spyOn(ComplexHelper, 'performCalculations').and.callFake( function () { return 'test'; } ); appCtrl.addResult(); expect(appCtrl.results[0]).toEqual('test'); }); });
Above, we’ve used the callFake() function to replace the standard behaviour of the ComplexHelper service with something much simpler that just returns the data that we expect. If there are any changes to the ComplexHelper service in the future our test will still work (you’d need to update the ComplexHelper unit tests though!). The above code looks quite simple and clean, but now imagine that there are lots of other controllers that make use of the service and lots of other functions in the service that would need to be mocked. Sure you could make structure the tests in such a way to make smart use of the beforeEach() function to minimize duplicate code. But this requires a lot more brain power than just creating a mocked module:
var mocks = angular.module('Mocks', []); mocks.service('ComplexHelper', function () { this.performCalculations = function () { return 'test'; } });
And plugging it into our tests:
describe('AppController', function () { beforeEach(module('App')); beforeEach(module('Mocks')); beforeEach(inject(function ($controller, _ComplexHelper_) { appCtrl = $controller('AppController'); ComplexHelper = _ComplexHelper_; })); it('should add get a complex result and add it to the array of results', function () { appCtrl.addResult(); expect(appCtrl.results[0]).toEqual('test'); }); });
And ta-da! We end up with a much leaner unit test, and a mock of our ComplexHelper service which we can reuse for any other test suite.
p.s. the mocked module needs to be plugged in after the actual app module, and the names of the dependencies inside the mocked module need to match the names in the actual app exactly so that they will automatically replace them.
Using this method has helped unravel the tangled web that our unit tests were becoming and actually makes me enjoy writing tests because it made me think about a more global solution rather than just quick fixes. Spies and $provide still have their place, but injecting a mocked module is much faster and cleaner when the same functionality needs to be mocked over and over again.
So what do you think of this method? Do you have your own way of making tests easier and more fun to write?
Re-writing or fixing broken code is not only frustratingly boring, it’s also a massive waste of time and money.
If you’re fed up of wasting resources on fixing pesky bugs then you need to learn how to test your code! My Testing AngularJS Applications course will teach you how to use Karma, Jasmine and Istanbul to create effective unit tests for your AngularJS applications so you can focus on the fun parts of development. And since you read this whole article, you can use promo code MK15 for a special price.
Leave a Reply