Avoid test code duplication in Jasmine tests

Treat your test code like your production code

Test code has to be treated like production code. Obviously we cannot charge the customer for it, it’s something that helps us developers to make sure we keep our codebase healthy, which ultimately is the responsibility we have towards our customers. Thus we need to apply the same best practices principles as we do for our production code, where, code duplication is evil.

Let’s quickly take a look at some Angular code and the corresponding Jasmine test. I have the following Angular Provider which holds some functionality for handling the application menu.

functionmenuProvider(){// expose the provider contractthis.addMenuEntry=addMenuEntry;...// expose the service contractthis.$get=function(){varservice={addMenuEntry:addMenuEntry...}returnservice;};///////////functionaddMenuEntry(newEntry){...}}

It’s not really important, but to understand the context, in Angular you have “Providers” and “Services”. The main difference is their availability during the application lifecycle (i.e. the config vs run phase). So basically if you want to have them available during both phases, you’d do something similar as I did above, namely to expose the exact same contract (or part of it) as a provider and as a service.

Obviously, I’d like to test the availability and correct functioning of this exposed contract on both, the provider and the service class. This leads to duplicated tests. Let’s see this on the example of this excerpt from a Jasmine test.

Refactoring duplications

Guess you clearly see the duplication. On the Pivotallabs site there’s a blog post “DRYing up Jasmine Specs with Shared Behavior” which describes the possibility to factor out your describe statement into a separate function:

functionsharedTests(someParams){describe(function(){...});}

You can then use that function simply by invoking it within your test code:

describe('My functionality',function(){...sharedTests(...);})

This works like charm, with one exception. Usually factoring out is useful to be able to parameterize the describe, in my case to use the same tests, the first time passing in a provider instance and then the service one. Like..

describe("The Menu's",function(){describe('provider interface',function(){varprovider;beforeEach(function(){provider=/* Angular code to inject the provider */;});// this is the line of interest!executeSharedTests(provider);});describe('service interface',function(){varservice;beforeEach(function(){service=/* Angular code to inject the service */});// this is the line of interest!executeSharedTests(service);});functionexecuteSharedTests(instance){...}});

This doesn’t work, for the simple reason that the beforeEach is executed after the executeSharedTests(...) is being invoked, thus passing in undefined.

To solve this problem you can pass in a constructor function which creates the object lazily when the test is effectively executed.

describe("The Menu's",function(){describe('service interface',function(){functioncreateInstance(){return/* Angular code to inject the service */}executedSharedTests(createInstance);});executedSharedTests(createInstanceFn){describe('when adding a new menu entry',function(){varsubjectUnderTest;beforeEach(function(){//create an instance by invoking the constructor functionsubjectUnderTest=createInstanceFn();});it('should allow to add new menu entries',function(){...});});}});