Внедрение макета в службу AngularJS
У меня есть служба AngularJS, и я хотел бы unit test it.
angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
factory('myService', function ($http, fooService, barService) {
this.something = function() {
// Do something with the injected services
};
return this;
});
В моем файле app.js зарегистрировано:
angular
.module('myApp', ['fooServiceProvider','barServiceProvider','myServiceProvider']
)
Я могу проверить, что DI работает как таковой:
describe("Using the DI framework", function() {
beforeEach(module('fooServiceProvider'));
beforeEach(module('barServiceProvider'));
beforeEach(module('myServiceProvder'));
var service;
beforeEach(inject(function(fooService, barService, myService) {
service=myService;
}));
it("can be instantiated", function() {
expect(service).not.toBeNull();
});
});
Это доказало, что сервис может быть создан каркасом DI, но затем я хочу unit test службу, что означает издевательство над вложенными объектами.
Как мне это сделать?
Я пробовал помещать свои модулированные объекты в модуль, например.
beforeEach(module(mockNavigationService));
и переписывая определение службы как:
function MyService(http, fooService, barService) {
this.somthing = function() {
// Do something with the injected services
};
});
angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
factory('myService', function ($http, fooService, barService) { return new MyService($http, fooService, barService); })
Но последнее, похоже, прекращает создание службы DI как все.
Кто-нибудь знает, как я могу издеваться над внедренными службами для своих модульных тестов?
Спасибо
Дэвид
Ответы
Ответ 1
Вы можете вводить mocks в свою службу, используя $provide
.
Если у вас есть следующая служба с зависимостью, которая имеет метод getSomething:
angular.module('myModule', [])
.factory('myService', function (myDependency) {
return {
useDependency: function () {
return myDependency.getSomething();
}
};
});
Вы можете ввести макетную версию myDependency следующим образом:
describe('Service: myService', function () {
var mockDependency;
beforeEach(module('myModule'));
beforeEach(function () {
mockDependency = {
getSomething: function () {
return 'mockReturnValue';
}
};
module(function ($provide) {
$provide.value('myDependency', mockDependency);
});
});
it('should return value from mock dependency', inject(function (myService) {
expect(myService.useDependency()).toBe('mockReturnValue');
}));
});
Обратите внимание, что из-за вызова $provide.value
вам не нужно явно вводить myDependency в любом месте. Это происходит под капотом во время инъекции myService. При настройке mockDependency здесь он мог бы также легко быть шпионом.
Спасибо loyalBrown за ссылку отличное видео.
Ответ 2
Как я смотрю на это, нет необходимости издеваться над самими услугами. Просто издевайтесь над функциями службы. Таким образом, вы можете использовать angular свои реальные услуги, как и во всем приложении. Затем, при необходимости используя функции Jasmine spyOn
, высмеивайте функции в сервисе.
Теперь, если сама служба - это функция, а не объект, с которым вы можете использовать spyOn
, есть и другой способ. Мне нужно было это сделать, и я нашел что-то, что хорошо работает для меня. См. Как вы издеваетесь над сервисом angular, который является функцией?
Ответ 3
Еще одна возможность помочь облегчить насмешливые зависимости в Angular и Jasmine - использовать QuickMock. Его можно найти на GitHub и позволяет создавать простые макеты в многоразовом режиме. Вы можете клонировать его из GitHub по ссылке ниже. README довольно понятно, но, надеюсь, это может помочь другим в будущем.
https://github.com/tennisgent/QuickMock
describe('NotificationService', function () {
var notificationService;
beforeEach(function(){
notificationService = QuickMock({
providerName: 'NotificationService', // the provider we wish to test
moduleName: 'QuickMockDemo', // the module that contains our provider
mockModules: ['QuickMockDemoMocks'] // module(s) that contains mocks for our provider dependencies
});
});
....
Он автоматически управляет всем описанным выше шаблоном, поэтому вам не нужно выписывать весь этот код ложных инъекций в каждом тесте. Надеюсь, что это поможет.
Ответ 4
В дополнение к ответ Джона Галамбоса: если вы просто хотите издеваться над определенными методами службы, вы можете сделать это следующим образом:
describe('Service: myService', function () {
var mockDependency;
beforeEach(module('myModule'));
beforeEach(module(function ($provide, myDependencyProvider) {
// Get an instance of the real service, then modify specific functions
mockDependency = myDependencyProvider.$get();
mockDependency.getSomething = function() { return 'mockReturnValue'; };
$provide.value('myDependency', mockDependency);
});
it('should return value from mock dependency', inject(function (myService) {
expect(myService.useDependency()).toBe('mockReturnValue');
}));
});
Ответ 5
Если ваш контроллер написан так, чтобы принимать зависимость, например:
app.controller("SomeController", ["$scope", "someDependency", function ($scope, someDependency) {
someDependency.someFunction();
}]);
то вы можете сделать подделку someDependency
в тесте Jasmine следующим образом:
describe("Some Controller", function () {
beforeEach(module("app"));
it("should call someMethod on someDependency", inject(function ($rootScope, $controller) {
// make a fake SomeDependency object
var someDependency = {
someFunction: function () { }
};
spyOn(someDependency, "someFunction");
// this instantiates SomeController, using the passed in object to resolve dependencies
controller("SomeController", { $scope: scope, someDependency: someDependency });
expect(someDependency.someFunction).toHaveBeenCalled();
}));
});
Ответ 6
Недавно я выпустил ngImprovedTesting, который должен сделать макетное тестирование в режиме AngularJS проще.
Чтобы проверить "myService" (из модуля "myApp" ) с помощью зависимостей fooService и barService, вы можете сделать следующее в своем тесте Jasmine:
beforeEach(ModuleBuilder
.forModule('myApp')
.serviceWithMocksFor('myService', 'fooService', 'barService')
.build());
Для получения дополнительной информации о ngImprovedTesting ознакомьтесь со своим вступительным сообщением в блоге: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/
Ответ 7
Я знаю, что это старый вопрос, но есть еще один более простой способ: вы можете создать mock и отключить исходный код, введенный в одну функцию, это можно сделать, используя spyOn для всех методов.
см. код ниже.
var mockInjectedProvider;
beforeEach(function () {
module('myModule');
});
beforeEach(inject(function (_injected_) {
mockInjectedProvider = mock(_injected_);
});
beforeEach(inject(function (_base_) {
baseProvider = _base_;
}));
it("injectedProvider should be mocked", function () {
mockInjectedProvider.myFunc.andReturn('testvalue');
var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
expect(resultFromMockedProvider).toEqual('testvalue');
});
//mock all service methods
function mock(angularServiceToMock) {
for (var i = 0; i < Object.getOwnPropertyNames(angularServiceToMock).length; i++) {
spyOn(angularServiceToMock,Object.getOwnPropertyNames(angularServiceToMock)[i]);
}
return angularServiceToMock;
}