(这里有一个相关问题:Jasmine test does not see AngularJS module)
我只是想在没有引导Angular的情况下测试服务。
我看了一些例子和教程,但我不会去任何地方。
我只有三个文件:
myService.js:我在哪里定义AngularJS服务
test_myService.js:我为服务定义了Jasmine测试。
specRunner.html:具有正常jasmine配置的HTML文件 我导入前两个其他文件和Jasmine,Angularjs和angular-mocks.js。
这是服务的代码(当我不测试时,它按预期工作):
var myModule = angular.module('myModule', []);
myModule.factory('myService', function(){
var serviceImplementation = {};
serviceImplementation.one = 1;
serviceImplementation.two = 2;
serviceImplementation.three = 3;
return serviceImplementation
});
当我试图孤立地测试服务时,我应该能够访问它并检查它们的方法。 我的问题是:如何在不引导AngularJS的情况下在我的测试中注入服务?
例如,我如何测试使用Jasmine的服务方法返回的值,如下所示:
describe('myService test', function(){
describe('when I call myService.one', function(){
it('returns 1', function(){
myModule = angular.module('myModule');
//something is missing here..
expect( myService.one ).toEqual(1);
})
})
});
答案 0 :(得分:137)
问题是在上面的示例中没有调用实例化服务的工厂方法(只创建模块不会实例化服务)。
为了实例化服务,必须使用定义服务的模块调用angular.injector。然后,我们可以向新的注入器对象请求服务,然后才能最终实例化服务。
这样的工作:
describe('myService test', function(){
describe('when I call myService.one', function(){
it('returns 1', function(){
var $injector = angular.injector([ 'myModule' ]);
var myService = $injector.get( 'myService' );
expect( myService.one ).toEqual(1);
})
})
});
另一种方法是使用“invoke”将服务传递给函数:
describe('myService test', function(){
describe('when I call myService.one', function(){
it('returns 1', function(){
myTestFunction = function(aService){
expect( aService.one ).toEqual(1);
}
//we only need the following line if the name of the
//parameter in myTestFunction is not 'myService' or if
//the code is going to be minify.
myTestFunction.$inject = [ 'myService' ];
var myInjector = angular.injector([ 'myModule' ]);
myInjector.invoke( myTestFunction );
})
})
});
最后,“正确”的方法是在'inject'茉莉花块中使用'module'和'beforeEach'。 当我们这样做时,我们必须意识到'注入'功能它不在标准的angularjs包中,而是在ngMock模块中,它只适用于茉莉。
describe('myService test', function(){
describe('when I call myService.one', function(){
beforeEach(module('myModule'));
it('returns 1', inject(function(myService){ //parameter name = service name
expect( myService.one ).toEqual(1);
}))
})
});
答案 1 :(得分:5)
虽然上面的答案可能工作得很好(我还没试过:)),我经常会有更多的测试,所以我不会自己注入测试。我将it()个案分组到describe块中,并在每个describe块中的beforeEach()或beforeAll()中运行我的注入。
罗伯特也是正确的,他说你必须使用Angular $注入器让测试知道服务或工厂。 Angular也会在您的应用程序中使用此注入器,以告知应用程序可用的内容。 但是,它可以在多个地方调用,也可以隐式地称为 而不是显式。您将在下面的示例规范测试文件中注意到,beforeEach()块隐式调用注入器,以便在测试内部分配可用的内容。回到分组事物并使用前块,这是一个小例子。我正在制作Cat服务而我想测试它,所以我编写和测试服务的简单设置如下所示:
<强> app.js 强>
var catsApp = angular.module('catsApp', ['ngMockE2E']);
angular.module('catsApp.mocks', [])
.value('StaticCatsData', function() {
return [{
id: 1,
title: "Commando",
name: "Kitty MeowMeow",
score: 123
}, {
id: 2,
title: "Raw Deal",
name: "Basketpaws",
score: 17
}, {
id: 3,
title: "Predator",
name: "Noseboops",
score: 184
}];
});
catsApp.factory('LoggingService', ['$log', function($log) {
// Private Helper: Object or String or what passed
// for logging? Let's make it String-readable...
function _parseStuffIntoMessage(stuff) {
var message = "";
if (typeof stuff !== "string") {
message = JSON.stringify(stuff)
} else {
message = stuff;
}
return message;
}
/**
* @summary
* Write a log statement for debug or informational purposes.
*/
var write = function(stuff) {
var log_msg = _parseStuffIntoMessage(stuff);
$log.log(log_msg);
}
/**
* @summary
* Write's an error out to the console.
*/
var error = function(stuff) {
var err_msg = _parseStuffIntoMessage(stuff);
$log.error(err_msg);
}
return {
error: error,
write: write
};
}])
catsApp.factory('CatsService', ['$http', 'LoggingService', function($http, Logging) {
/*
response:
data, status, headers, config, statusText
*/
var Success_Callback = function(response) {
Logging.write("CatsService::getAllCats()::Success!");
return {"status": status, "data": data};
}
var Error_Callback = function(response) {
Logging.error("CatsService::getAllCats()::Error!");
return {"status": status, "data": data};
}
var allCats = function() {
console.log('# Cats.allCats()');
return $http.get('/cats')
.then(Success_Callback, Error_Callback);
}
return {
getAllCats: allCats
};
}]);
var CatsController = function(Cats, $scope) {
var vm = this;
vm.cats = [];
// ========================
/**
* @summary
* Initializes the controller.
*/
vm.activate = function() {
console.log('* CatsCtrl.activate()!');
// Get ALL the cats!
Cats.getAllCats().then(
function(litter) {
console.log('> ', litter);
vm.cats = litter;
console.log('>>> ', vm.cats);
}
);
}
vm.activate();
}
CatsController.$inject = ['CatsService', '$scope'];
catsApp.controller('CatsCtrl', CatsController);
规范:猫控制器
'use strict';
describe('Unit Tests: Cats Controller', function() {
var $scope, $q, deferred, $controller, $rootScope, catsCtrl, mockCatsData, createCatsCtrl;
beforeEach(module('catsApp'));
beforeEach(module('catsApp.mocks'));
var catsServiceMock;
beforeEach(inject(function(_$q_, _$controller_, $injector, StaticCatsData) {
$q = _$q_;
$controller = _$controller_;
deferred = $q.defer();
mockCatsData = StaticCatsData();
// ToDo:
// Put catsServiceMock inside of module "catsApp.mocks" ?
catsServiceMock = {
getAllCats: function() {
// Just give back the data we expect.
deferred.resolve(mockCatsData);
// Mock the Promise, too, so it can run
// and call .then() as expected
return deferred.promise;
}
};
}));
// Controller MOCK
var createCatsController;
// beforeEach(inject(function (_$rootScope_, $controller, FakeCatsService) {
beforeEach(inject(function (_$rootScope_, $controller, CatsService) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
createCatsController = function() {
return $controller('CatsCtrl', {
'$scope': $scope,
CatsService: catsServiceMock
});
};
}));
// ==========================
it('should have NO cats loaded at first', function() {
catsCtrl = createCatsController();
expect(catsCtrl.cats).toBeDefined();
expect(catsCtrl.cats.length).toEqual(0);
});
it('should call "activate()" on load, but only once', function() {
catsCtrl = createCatsController();
spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);
// *** For some reason, Auto-Executing init functions
// aren't working for me in Plunkr?
// I have to call it once manually instead of relying on
// $scope creation to do it... Sorry, not sure why.
catsCtrl.activate();
$rootScope.$digest(); // ELSE ...then() does NOT resolve.
expect(catsCtrl.activate).toBeDefined();
expect(catsCtrl.activate).toHaveBeenCalled();
expect(catsCtrl.activate.calls.count()).toEqual(1);
// Test/Expect additional conditions for
// "Yes, the controller was activated right!"
// (A) - there is be cats
expect(catsCtrl.cats.length).toBeGreaterThan(0);
});
// (B) - there is be cats SUCH THAT
// can haz these properties...
it('each cat will have a NAME, TITLE and SCORE', function() {
catsCtrl = createCatsController();
spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);
// *** and again...
catsCtrl.activate();
$rootScope.$digest(); // ELSE ...then() does NOT resolve.
var names = _.map(catsCtrl.cats, function(cat) { return cat.name; })
var titles = _.map(catsCtrl.cats, function(cat) { return cat.title; })
var scores = _.map(catsCtrl.cats, function(cat) { return cat.score; })
expect(names.length).toEqual(3);
expect(titles.length).toEqual(3);
expect(scores.length).toEqual(3);
});
});
规范:猫服务
'use strict';
describe('Unit Tests: Cats Service', function() {
var $scope, $rootScope, $log, cats, logging, $httpBackend, mockCatsData;
beforeEach(module('catsApp'));
beforeEach(module('catsApp.mocks'));
describe('has a method: getAllCats() that', function() {
beforeEach(inject(function($q, _$rootScope_, _$httpBackend_, _$log_, $injector, StaticCatsData) {
cats = $injector.get('CatsService');
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
// We don't want to test the resolving of *actual data*
// in a unit test.
// The "proper" place for that is in Integration Test, which
// is basically a unit test that is less mocked - you test
// the endpoints and responses and APIs instead of the
// specific service behaviors.
mockCatsData = StaticCatsData();
// For handling Promises and deferrals in our Service calls...
var deferred = $q.defer();
deferred.resolve(mockCatsData); // always resolved, you can do it from your spec
// jasmine 2.0
// Spy + Promise Mocking
// spyOn(obj, 'method'), (assumes obj.method is a function)
spyOn(cats, 'getAllCats').and.returnValue(deferred.promise);
/*
To mock $http as a dependency, use $httpBackend to
setup HTTP calls and expectations.
*/
$httpBackend.whenGET('/cats').respond(200, mockCatsData);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
})
it(' exists/is defined', function() {
expect( cats.getAllCats ).toBeDefined();
expect( typeof cats.getAllCats ).toEqual("function");
});
it(' returns an array of Cats, where each cat has a NAME, TITLE and SCORE', function() {
cats.getAllCats().then(function(data) {
var names = _.map(data, function(cat) { return cat.name; })
var titles = _.map(data, function(cat) { return cat.title; })
var scores = _.map(data, function(cat) { return cat.score; })
expect(names.length).toEqual(3);
expect(titles.length).toEqual(3);
expect(scores.length).toEqual(3);
})
});
})
describe('has a method: getAllCats() that also logs', function() {
var cats, $log, logging;
beforeEach(inject(
function(_$log_, $injector) {
cats = $injector.get('CatsService');
$log = _$log_;
logging = $injector.get('LoggingService');
spyOn(cats, 'getAllCats').and.callThrough();
}
))
it('that on SUCCESS, $logs to the console a success message', function() {
cats.getAllCats().then(function(data) {
expect(logging.write).toHaveBeenCalled();
expect( $log.log.logs ).toContain(["CatsService::getAllCats()::Success!"]);
})
});
})
});
修改强> 根据一些评论,我更新了我的答案稍微复杂一点,我也组成了一个Plunkr演示单元测试。 具体来说,其中一条评论&#34;如果控制器服务本身具有简单的依赖性,例如$ log?&#34; - 包含在测试用例中的示例。 希望能帮助到你!测试或破坏地球!!!
答案 2 :(得分:0)
我需要测试一个需要另一个指令Google Places Autocomplete的指令,我正在讨论是否应该只是嘲笑它...无论如何,这对于需要gPlacesAutocomplete的指令抛出任何错误都有效。
describe('Test directives:', function() {
beforeEach(module(...));
beforeEach(module(...));
beforeEach(function() {
angular.module('google.places', [])
.directive('gPlacesAutocomplete',function() {
return {
require: ['ngModel'],
restrict: 'A',
scope:{},
controller: function() { return {}; }
};
});
});
beforeEach(module('google.places'));
});
答案 3 :(得分:-5)
如果你想测试一个控制器,你可以注射并测试它,如下所示。
describe('When access Controller', function () {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function (_$controller_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.objectState', function () {
it('is saying hello', function () {
var $scope = {};
var controller = $controller('yourController', { $scope: $scope });
expect($scope.objectState).toEqual('hello');
});
});
});