Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I provide re-usable sample data values to my angularjs / jasmine unit-tests

I would like to provide simple constant values such as names, emails, etc to use in my jasmine unit tests.

A similar question was asked here: AngularJS + Karma: reuse a mock service when unit testing directives or controllers

In c# I would create a static class to hold little snippets of mock data. I can then use these values throughout my unit tests, like this:

static class SampleData
{
    public const string Guid = "0e3ae555-9fc7-4b89-9ea4-a8b63097c50a";
    public const string Password = "3Qr}b7_N$yZ6";

    public const string InvalidGuid = "[invalid-guid]";
    public const string InvalidPassword = "[invalid-password]"; 
}

I would like to have the same convenience when testing my AngularJS app using Karma / Jasmine.

I know that I can define a constant object against my angular app, I already do this for constants I use in the real code, like this:

myApp.constant('config', {apiUrl:'http://localhost:8082'})

I could add another constant just like this but only containing sample data values for use in my unit tests, like this:

myApp.constant('sampleData', {email: '[email protected]'}) 

I could then just inject the mock constant object into my tests and off I go, like this

describe 'A sample unit test', ->
    beforeEach ->   module 'myApp'
    beforeEach inject ($injector) ->
        @sampleData = $injector.get 'sampleData'
        email = @sampleData.email
        # etc ...

However this seems a bit fishy to me. I don't want my production code to contain sample data that is only required by my unit-tests.

How would you conveniently provide your angular / jasmine unit tests with re-usable sample data values?

Thanks

like image 929
biofractal Avatar asked Oct 30 '13 10:10

biofractal


1 Answers

There are two ways of doing this:

  • spy on function calls and return fake values.
  • create mock classes (and possibly mock data to initialise them) and load them wherever you need

The first one is alright when you only have to fake a few calls. doing that for a whole class is unsustainable.

For example, let's say you have a service that builds some special URLs. If one of the methods depends on absUrl, you can fake it by spying on the method in the $location object:

describe('example') function () {
    beforeEach(inject(function () {
        spyOn($location, 'absUrl').andCallFake(function (p) {
            return 'http://localhost/app/index.html#/chickenurl';
        });
    }));
it('should return the url http://www.chicken.org') ... {
    // starting situation
    // run the function
    // asserts
}

Now let's say that you have a Settings Service that encapsulates data like language, theme, special paths, background color, typeface... that is initialised using a remote call to a server. Testing services that depend on Settings will be painful. You have to mock a big component with spyOn every time. If you have 10 services... you don't want to copypaste the spyOn functions in all of them.

ServiceA uses Settings service:

describe('ServiceA', function () {
var settings, serviceA;

beforeEach(module('myapp.mocks.settings'));  // mock settings
beforeEach(module('myapp.services.serviceA')); // load the service being tested

beforeEach(inject(function (_serviceA_, _settings_) {
    serviceA = _serviceA_;
    settings = _settings_;
}));

container for this test suite, all calls to the Settings service will be handled by the mock, which has the same interface as the real one, but returns dummy values. Notice that you can load this service anywhere.

(If, by any reason, you needed to use the real implementation, you can load the real implementation before the mock and use spyOn for that particular case to delegate the call to the real implementation.)

Normally you'll place the mocks module outside of the app folder. I have a test folder with the unit tests, e2e tests and a lib folder with the angular-mocks.js file. I place my mocks there too. Tell karma the files you need for the tests:

    files: [
      'app/lib/jquery/jquery-1.9.1.js',
      'test/lib/jasmine-jquery.js',
      'app/lib/angular/angular.js',
      'app/lib/angular/angular-*.js',
      'test/lib/angular/angular-mocks.js',
      'test/lib/myapp/*.js', /* this is mine */
      'app/js/**/*.js',
      'test/unit/**/*.js'
    ],

The file tests/lib/myapp.mocks.settings.js looks just like any other module:

(function () {
"use strict";

var m = angular.module('myapp.mocks.settings', []);

m.service('settings', function () { ... })
})

Second problem (optional): you want to change quickly the dummy values. In the example, the settings service fetches an object from the server when it is instantiated for the first time. then, the service has getters for all fields. This is kind of a proxy to the server: instead of sending a request everytime you need a value, fetch a bunch of them and save them locally. In my application, settings don't change in the server in run-time.

Something like:

1. fetch http://example.org/api/settings
2. var localSettings = fetchedSettings;
3  getFieldA: function() { return localSettings.fieldA; }

Go and pollute the global namespace. I created a file settings.data.js with a content like:

var SETTINGS_RESPONSE = { fieldA: 42 };

the mock service uses this global variable in the factory to instantiate localSettings

like image 59
Eduard Gamonal Avatar answered Sep 28 '22 07:09

Eduard Gamonal