Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is $provide only available in the 'angular.mock.module' function, and $q only available in the 'angular.mock.inject' function?

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 the angular.mock.inject function below. But if I add $provide as a parameter to the angular.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 the angular.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 in module functions.

like image 793
Holf Avatar asked Oct 10 '13 13:10

Holf


People also ask

What does Angular mock module do?

A mock module provides: mocks of all components, directives, pipes and providers. mocks of all imports and exports. dummy clones of all services.

Which is used to inject a service into a test function?

To test a service, you set the providers metadata property with an array of the services that you'll test or mock. content_copy let service: ValueService; beforeEach(() => { TestBed. configureTestingModule({ providers: [ValueService] }); }); Then inject it inside a test by calling TestBed.


2 Answers

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.

like image 95
Michael Benford Avatar answered Oct 20 '22 22:10

Michael Benford


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.

like image 25
Claudiu Avatar answered Oct 20 '22 21:10

Claudiu