在 Karma + AngularJS 测试中加载模拟 JSON 文件

我有一个 AngularJS 应用程序设置了使用 Karma + Jasmine 的测试。我想测试一个函数,它接受一个大型 JSON 对象,将其转换为应用程序其余部分更易于使用的格式,并返回转换后的对象。就是这样。

对于我的测试,我希望您有单独的 JSON 文件(* 。JSON) ,只包含模拟 JSON 内容——没有脚本。对于测试,我希望能够加载 JSON 文件并将对象输入到我正在测试的函数中。

我知道我可以像这里描述的那样将 JSON 嵌入到一个模拟工厂中: http://dailyjs.com/2013/05/16/angularjs-5/,但是我真的希望 JSON 不包含在脚本中——只包含直接的 JSON 文件。

我尝试了一些方法,但在这方面我还是相当菜的。首先,我将 Karma 设置为包含 JSON 文件,看看它会做什么:

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

结果是:

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

所以我修改了它,只提供文件而不是“包含”它们:

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

现在他们只是服务,我想我会尝试加载文件使用 $http 从我的规范:

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

当我运行我收到的规格:

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

在我的理解中,这意味着它期望来自 $httpBackend 的模拟响应。所以... 在这一点上,我不知道如何加载文件使用 Angular 实用程序,所以我想我应该尝试 jQuery,看看我是否至少可以让它工作:

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

结果是:

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

我在查尔斯检查这个请求

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

而我配置为被 Karma“包含”的其他文件被请求,例如:

/base/src/app.js

显然卡玛在建立某种基本目录来提供文件。因此,我将 jquery 数据请求更改为

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

它确实起作用了! 但是现在我觉得很脏,需要洗个澡。帮助我再次感到干净。

72721 次浏览

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).

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
....
}

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

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

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

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.

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

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