Why is $provide only available in the 'angular.mock.module' function, and $q only available in the 'angular.mock.inject' function?
Solution 1
You can't use $provide
within the inject
function because the former registers providers for the latter to use. Take a look:
describe('...', function() {
beforeEach(function() {
module(function($provide) {
$provide.constant('someValue', 'foobar');
});
inject(function(someValue) {
var value = someValue; // will be 'foobar';
});
});
});
You can though write your test this way:
describe('...', function() {
var serviceMock;
beforeEach(function() {
serviceMock = {
someMethod: function() { ... }
};
module(function($provide) {
$provide.value('service', serviceMock);
});
inject(function(service) {
...
});
});
});
In fact, you don't even need to implement the mocked service before injecting it with $provide
:
beforeEach(function() {
serviceMock = {};
module(function($provide) {
$provide.value('service', serviceMock);
});
inject(function(service) {
...
});
});
it('tests something', function() {
// Arrange
serviceMock.someMethod = function() { ... }
// Act
// does something
// Assert
expect(...).toBe(...);
});
Here's a Plunker script illustrating mostly of the above.
Solution 2
This worked for me when I had to wrap a service which used $q
and seems quite clean:
var _ServiceToTest_;
beforeEach(function () {
module('module.being.tested');
module(function ($provide) {
$provide.factory('ServiceToMock', function ($q, $rootScope) {
var service = ...;
// use $q et al to heart's content
return service;
});
});
inject(function (_ServiceToTest_) {
ServiceToTest = _ServiceToTest_;
});
});
it('...', function () { /* code using ServiceToTest */ });
The trick was to use $provide.factory
instead of $provide.value
.
Holf
Updated on July 25, 2022Comments
-
Holf almost 2 years
I am mocking out a service for an AngularJS Unit Test. I'm using the
$provide
service to replace the 'real' service with the mocked out one (a plunker script of this is available):describe('My Controller', function () { var $scope; var $provide; beforeEach(angular.mock.module('myApp')); beforeEach(angular.mock.module(function (_$provide_) { $provide = _$provide_; })); beforeEach(angular.mock.inject(function($rootScope, $controller, $q){ var mockMyService = { getAll : function() { var deferred = $q.defer(); deferred.resolve([ { itemText: "Foo" }, { itemText: "Bar" } ]); return deferred.promise; } }; $provide.value('myService', mockMyService); $scope = $rootScope.$new(); $controller('MyCtrl', { $scope: $scope }); $rootScope.$apply(); })); it('Has two items defined', function () { expect($scope.items.length).toEqual(2); }); });
This works just fine. However, I don't like the fact that I am using an
angular.mock.module
function simply to give a reference to the$provide
service which is then used in theangular.mock.inject
function below. But if I add$provide
as a parameter to theangular.mock.inject
function directly instead, I get an 'unknown provider' error.It occurs to me that I could put all the mocking code in the
angular.mock.module
function. But then I have a similar issue with the$q
reference, which I need as my mocked service has to return a promise.In other words, if I add a
$q
parameter to theangular.mock.module
function then I also get an 'unknown provider' error.Is there a way to simplify this? Obviously what I have works but it doesn't feel quite right, somehow. I feel that I lack understanding of why some providers are available in
inject
functions and others are available inmodule
functions. -
Holf over 10 yearsThis is very nice and it works... as long as your not using a Promise. Here is a plunk showing my original example: plnkr.co/edit/1Gbr1N?p=preview And here is a fork updated with the technique you suggest: plnkr.co/edit/ptAWcb?p=preview The mock service is not getting assigned; it looks as though something is interfering with the timing.
-
Holf over 10 yearsI wonder why I can't get hold of the '$q' library from the 'module' function? If I could, then I would have everything I need to create the mock service there.
-
Michael Benford over 10 years@Holf You can use a promise, but you need to move things around a little bit first. Check out this updated Plunker script.
-
Michael Benford over 10 years@Holf You can't get a instance of the
$q
service within themodule
function because Angular doesn't inject any service into it. The proper way to do that is by using theinject
function. That's by design, as far as I can tell. -
Holf over 10 yearsThanks, this is all really cool and it now makes a lot more sense.
-
FutuToad over 10 years@MichaelBenford great answer! btw I was wondering what was the diff between angular.mock.module vs angular.module
-
Michael Benford over 10 years@FutuToad
angular.module
is used to define a module whileangular.mock.module
is used to load an existing module into memory so tests can get access to its content (services, factories, directives etc).mock.module
is also used to configure the injector so you can provide mock objects for any injectable service. Take a look at the Angular docs for more information. -
FutuToad over 10 years@MichaelBenford thx, but in my tests: beforeEach(angular.mock.module('app')); makes all my tests pass just as when I do: beforeEach(module('app'));
-
Michael Benford over 10 years@FutuToad
module
is a global alias forangular.mock.module
, as said in the docs:This function is also published on window for easy access.
. -
ravishi almost 9 yearsI studied the differences between Holf's plnkr and @michael-benford's plnkr to figure out why Michael's worked and why Holf's didn't. At first, I thought it was due to the arrangement order of Michael's code but it turns out there was a simple error in Holf's. Holf's just needed to initialize mockMyService to an empty object and then not overwrite it when defining the getAll mocked method. That way the provided mock service gets used. Updated Holf's plnkr.