Loading a mock JSON file within Karma+AngularJS test

AngularjsKarma Runner

Angularjs Problem Overview


I have an AngularJS app set up with tests using Karma+Jasmine. I have a function I want to test that takes a large JSON object, converts it to a format that's more consumable by the rest of the app, and returns that converted object. That's it.

For my tests, I'd like you have separate JSON files (*.json) with mock JSON content only--no script. For the test, I'd like to be able to load the JSON file in and pump the object into the function I'm testing.

I know I can embed the JSON within a mock factory as described here: http://dailyjs.com/2013/05/16/angularjs-5/ but I really want the JSON to not be contained within script--just straight JSON files.

I've tried a few things but I'm fairly noob in this area. First, I set up my Karma to include my JSON file just to see what it would do:

files = [
    ...
    'mock-data/**/*.json'
    ...
]

This resulted in:

Chrome 27.0 (Mac) ERROR
Uncaught SyntaxError: Unexpected token :
at /Users/aaron/p4workspace4/depot/sitecatalyst/branches/anomaly_detection/client/anomaly-detection/mock-data/two-metrics-with-anomalies.json:2

So then I changed it to just serve the files and not "include" them:

files = [
    ...
    { pattern: 'mock-data/**/*.json', included: false }
    ...
]

Now that they're only served, I thought I'd try to load in the file using $http from within my spec:

$http('mock-data/two-metrics-with-anomalies.json')

When I ran the spec I received:

Error: Unexpected request: GET mock-data/two-metrics-with-anomalies.json

Which in my understanding means it expects a mocked response from $httpBackend. So...at this point I didn't know how to load the file using Angular utilities so I thought I'd try jQuery to see if I could at least get that to work:

$.getJSON('mock-data/two-metrics-with-anomalies.json').done(function(data) {
    console.log(data);
}).fail(function(response) {
    console.log(response);
});

This results in:

Chrome 27.0 (Mac) LOG: { readyState: 4,
responseText: 'NOT FOUND',
status: 404,
statusText: 'Not Found' }

I inspect this request in Charles and it's making a request to

/mock-data/two-metrics-with-anomalies.json

Whereas the rest of the files I've configured to be "included" by Karma are being requested at, for example:

/base/src/app.js

Apparently Karma's setting up some sort of base directory to serve the files from. So for kicks I changed my jquery data request to

$.getJSON('base/mock-data/two-metrics-with-anomalies.json')...

And it works! But now I feel dirty and need to take a shower. Help me feel clean again.

Angularjs Solutions


Solution 1 - Angularjs

I'm using an angular setup with angular seed. I ended up solving this with straight .json fixture files and jasmine-jquery.js. Others had alluded to this answer, but it took me a while to get all the pieces in the right place. I hope this helps someone else.

I have my json files in a folder /test/mock and my webapp is in /app.

my karma.conf.js has these entries (among others):

basePath: '../',

files: [
      ... 
      'test/vendor/jasmine-jquery.js',
      'test/unit/**/*.js',

      // fixtures
      {pattern: 'test/mock/*.json', watched: true, served: true, included: false}
    ],

then my test file has:

describe('JobsCtrl', function(){
var $httpBackend, createController, scope;

beforeEach(inject(function ($injector, $rootScope, $controller) {

    $httpBackend = $injector.get('$httpBackend');
    jasmine.getJSONFixtures().fixturesPath='base/test/mock';

    $httpBackend.whenGET('http://blahblahurl/resultset/').respond(
        getJSONFixture('test_resultset_list.json')
    );

    scope = $rootScope.$new();
    $controller('JobsCtrl', {'$scope': scope});

}));


it('should have some resultsets', function() {
    $httpBackend.flush();
    expect(scope.result_sets.length).toBe(59);
});

});

The real trick was the jasmine.getJSONFixtures().fixturesPath='base/test/mock'; I had originally set it to just test/mock but it needed the base in there. Without the base, I got errors like this:

Error: JSONFixture could not be loaded: /test/mock/test_resultset_list.json (status: error, message: undefined)
at /Users/camd/gitspace/treeherder-ui/webapp/test/vendor/jasmine-jquery.js:295

Solution 2 - Angularjs

Serving JSON via the fixture is the easiest but because of our setup we couldn't do that easily so I wrote an alternative helper function:

Repository

Install

$ bower install karma-read-json --save

  OR

$ npm install karma-read-json --save-dev

  OR

$ yarn add karma-read-json --dev

Usage

  1. Put karma-read-json.js in your Karma files. Example:

     files = [
       ...
       'bower_components/karma-read-json/karma-read-json.js',
       ...
     ]
    
  2. Make sure your JSON is being served by Karma. Example:

     files = [
       ...
       {pattern: 'json/**/*.json', included: false},
       ...
     ]
    
  3. Use the readJSON function in your tests. Example:

     var valid_respond = readJSON('json/foobar.json');
     $httpBackend.whenGET(/.*/).respond(valid_respond);
    

Solution 3 - Angularjs

I've been struggling to find a solution to loading external data into my testcases. The above link: http://dailyjs.com/2013/05/16/angularjs-5/ Worked for me.

Some notes:

"defaultJSON" needs to be used as the key in your mock data file, this is fine, as you can just refer to defaultJSON.

mockedDashboardJSON.js:

'use strict'
angular.module('mockedDashboardJSON',[])
.value('defaultJSON',{
    fakeData1:{'really':'fake2'},
    fakeData2:{'history':'faked'}
});

Then in your test file:

beforeEach(module('yourApp','mockedDashboardJSON'));
var YourControlNameCtrl, scope, $httpBackend, mockedDashboardJSON;
beforeEach(function(_$httpBackend_,defaultJSON){
    $httpBackend.when('GET','yourAPI/call/here').respond(defaultJSON.fakeData1);
    //Your controller setup 
    ....
});

it('should test my fake stuff',function(){
    $httpBackend.flush();
    //your test expectation stuff here
    ....
}

Solution 4 - Angularjs

looks like your solution is the right one but there are 2 things i don't like about it:

  • it uses jasmine
  • it requires new learning curve

i just ran into this problem and had to resolve it quickly as i had no time left for the deadline, and i did the following

my json resource was huge, and i couldn't copy paste it into the test so i had to keep it a separate file - but i decided to keep it as javascript rather than json, and then i simply did:

var someUniqueName = ... the json ...

and i included this into karma conf includes..

i can still mock a backend http response if needed with it.

$httpBackend.whenGET('/some/path').respond(someUniqueName);

i could also write a new angular module to be included here and then change the json resource to be something like

angular.module('hugeJsonResource', []).constant('SomeUniqueName', ... the json ... );

and then simply inject SomeUniqueName into the test, which looks cleaner.

perhaps even wrap it in a service

angular.module('allTestResources',[]).service('AllTestResources', function AllTestResources( SomeUniqueName , SomeOtherUniqueName, ... ){
   this.resource1 = SomeUniqueName;
   this.resource2 = SomeOtherUniqueName; 
})

this solutions was faster to me, just as clean, and did not require any new learning curve. so i prefer this one.

Solution 5 - Angularjs

I was looking for the same thing. I'm going to try this approach. It uses the config files to include the mock data files, but the files are a little more than json, because the json needs to be passed to angular.module('MockDataModule').value and then your unit tests can also load multiple modules and then the value set is available to be injected into the beforeEach inject call.

Also found another approach that looks promising for xhr requests that aren't costly, it's a great post that describes midway testing, which if I understand right lets your controller/service actually retrieve data like in an e2e test, but your midway test has actual access to the controller scope (e2e doesn't I think).

Solution 6 - Angularjs

There are Karma preprocessors that work with JSON files also. There is one here:

https://www.npmjs.org/package/karma-ng-json2js-preprocessor

And shameless plug, this is one I developed that has RequireJS support

https://www.npmjs.org/package/karma-ng-json2js-preprocessor-requirejs

Solution 7 - Angularjs

You can use the karma-html2js-preprocessor to get the json files added to the _html_ global.

see this answer for details: https://stackoverflow.com/a/22103160/439021

Solution 8 - Angularjs

Here is an alternative to Cameron's answer, without the need for jasmine-jquery nor any extra Karma config, to test e.g. an Angular service using $resource :

angular.module('myApp').factory('MyService', function ($resource) {
    var Service = $resource('/:user/resultset');
    return {
        getResultSet: function (user) {
            return Service.get({user: user}).$promise;
        }
    };
});

And the corresponding unit test :

describe('MyServiceTest', function(){
    var $httpBackend, MyService, testResultSet, otherTestData ;

    beforeEach(function (done) {
        module('myApp');
        inject(function ($injector) {
            $httpBackend = $injector.get('$httpBackend');
            MyService = $injector.get('MyService');
        });
        // Loading fixtures
        $.when(
            $.getJSON('base/test/mock/test_resultset.json', function (data) { testResultSet = data; }),
            $.getJSON('base/test/mock/test_other_data.json', function (data) { otherTestData = data; })
        ).then(done);
    });

    it('should have some resultset', function() {
        $httpBackend.expectGET('/blahblahurl/resultset').respond(testResultSet);
        MyService.getResultSet('blahblahurl').then(function (resultSet) {
            expect(resultSet.length).toBe(59);
        });
        $httpBackend.flush();
    });
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAaroniusView Question on Stackoverflow
Solution 1 - AngularjsCameronView Answer on Stackoverflow
Solution 2 - AngularjsPizzaPantherView Answer on Stackoverflow
Solution 3 - AngularjsTOBlenderView Answer on Stackoverflow
Solution 4 - Angularjsguy mograbiView Answer on Stackoverflow
Solution 5 - Angularjsuser1683523View Answer on Stackoverflow
Solution 6 - AngularjssmaView Answer on Stackoverflow
Solution 7 - AngularjskenglxnView Answer on Stackoverflow
Solution 8 - AngularjsLucas CimonView Answer on Stackoverflow