我如何用JasminetestingAngularJS服务?

(这里有一个相关的问题: Jasminetesting没有看到AngularJS模块 )

我只是想testing一个服务,而不需要引导Angular。

我看了一些例子和教程,但我不会去任何地方。

我只有三个文件:

  • myService.js:我在哪里定义一个AngularJS服务

  • test_myService.js:我为服务定义一个Jasminetesting。

  • specRunner.html:一个具有普通茉莉花configuration的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 }); 

因为我试图孤立地testing服务,所以我应该能够访问它并检查它们的方法。 我的问题是:如何在不引导AngularJS的情况下将服务注入到我的testing中?

例如,我怎样才能testing这样的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); }) }) }); 

问题是实例化服务的工厂方法在上面的例子中没有被调用(只有创build模块没有实例化服务)。

为了使服务被实例化, 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 ); }) }) }); 

最后,“正确”的做法是在“ 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); })) }) }); 

虽然上面的答案可能工作得很好(我没有试过:)),我经常有更多的testing运行,所以我不注入testing本身。 我将把它分成几个描述块,并在每个描述块的beforeEach()或beforeAll()中运行我的注入。

罗伯特也是正确的,他说你必须使用Angular $注入器使testing意识到服务或工厂。 Angular也在你的应用程序中使用这个注入器来告诉应用程序有什么可用的东西。 但是,它可以在多个地方被调用,也可以隐式调用,而不是显式调用。 你会注意到在我的示例spec文件下面,beforeEach()块隐式地调用注入器来使事情在testing中被分配。

回过头来分组和使用之前的块,这里是一个小例子。 我正在做一个Cat服务,我想testing它,所以我编写和testing服务的简单设置如下所示:

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演示unit testing。 具体来说,其中一个评论提到“如果一个控制器的服务本身有一个简单的依赖,如$ log? – 包含在testing用例的例子中。 希望能帮助到你! testing或破解行星!

https://embed.plnkr.co/aSPHnr/

我需要testing一个指令,需要另一个指令, 谷歌地方自动完成 ,我在辩论我是否应该嘲笑它…反正这个工作与抛出任何错误的指令,需要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')); }); 

如果你想testing一个控制器,你可以注入并testing它如下。

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