Using $provide to override dependencies in tests

Loading

When testing AngularJS applications, we often need to override services, factories, or constants to control their behavior during tests. AngularJS provides the $provide service to mock dependencies efficiently. This is useful for:

✔ Mocking services that make HTTP requests.
✔ Replacing complex logic with simple test implementations.
✔ Controlling external dependencies to make tests predictable.


1. What is $provide?

$provide is an AngularJS service that allows us to:

  • Register new dependencies.
  • Override existing services, factories, or constants.
  • Replace dependencies with mocks for unit testing.

We use $provide inside module() before injecting dependencies.


2. Setting Up Karma and Jasmine

First, ensure you have Karma and Jasmine installed:

npm install karma karma-jasmine jasmine-core angular-mocks --save-dev

Include angular-mocks.js in karma.conf.js:

files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'app.js',
'services/*.js',
'controllers/*.js',
'tests/**/*.spec.js'
]

3. Overriding a Service Using $provide

Example: Original Service (userService.js)

app.service('UserService', function($http) {
this.getUser = function() {
return $http.get('/api/user');
};
});

Test: Mocking UserService (userService.spec.js)

describe('UserService Test with $provide', function() {
var UserService;

beforeEach(module('myApp', function($provide) {
$provide.service('UserService', function() {
this.getUser = function() {
return { id: 1, name: "Mock User" };
};
});
}));

beforeEach(inject(function(_UserService_) {
UserService = _UserService_;
}));

it('should return mocked user data', function() {
var user = UserService.getUser();
expect(user).toEqual({ id: 1, name: "Mock User" });
});
});

Explanation

  • $provide.service('UserService', function() {...}) → Overrides the original service.
  • Mock implementation replaces $http call with static data.
  • Ensures predictable test results.

4. Overriding a Factory Using $provide

Example: Original Factory (authFactory.js)

app.factory('AuthFactory', function($http) {
return {
login: function(credentials) {
return $http.post('/api/login', credentials);
}
};
});

Test: Mocking AuthFactory (authFactory.spec.js)

describe('AuthFactory Test with $provide', function() {
var AuthFactory;

beforeEach(module('myApp', function($provide) {
$provide.factory('AuthFactory', function() {
return {
login: function() {
return { token: "mockToken123" };
}
};
});
}));

beforeEach(inject(function(_AuthFactory_) {
AuthFactory = _AuthFactory_;
}));

it('should return mocked login response', function() {
var response = AuthFactory.login();
expect(response).toEqual({ token: "mockToken123" });
});
});

Key Takeaways

Factory methods can be overridden using $provide.factory().
Mocks should return simple, predictable values to validate test logic.
This avoids unnecessary API calls and speeds up testing.


5. Overriding Constants Using $provide

Example: Original Constant (config.js)

app.constant('API_URL', 'https://real-api.com');

Test: Mocking API_URL (config.spec.js)

describe('Mocking Constants with $provide', function() {
var API_URL;

beforeEach(module('myApp', function($provide) {
$provide.constant('API_URL', 'https://mock-api.com');
}));

beforeEach(inject(function(_API_URL_) {
API_URL = _API_URL_;
}));

it('should override API_URL constant', function() {
expect(API_URL).toBe('https://mock-api.com');
});
});

Why Mock Constants?

✔ Avoid external API calls in tests.
✔ Test different environments dynamically.


6. Overriding Services for Controllers

Example: Controller Using UserService (userController.js)

app.controller('UserController', function($scope, UserService) {
UserService.getUser().then(function(response) {
$scope.user = response.data;
});
});

Test: Mocking UserService in Controller (userController.spec.js)

describe('UserController Test with $provide', function() {
var $controller, $rootScope;

beforeEach(module('myApp', function($provide) {
$provide.service('UserService', function() {
this.getUser = function() {
return {
then: function(callback) {
return callback({ data: { id: 1, name: "Mock User" } });
}
};
};
});
}));

beforeEach(inject(function(_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
}));

it('should set $scope.user with mocked user data', function() {
var $scope = $rootScope.$new();
$controller('UserController', { $scope: $scope });

expect($scope.user).toEqual({ id: 1, name: "Mock User" });
});
});

Key Concepts

Mock then() method to simulate promise resolution.
Ensures controllers behave correctly without real API calls.


7. Best Practices for Using $provide

Keep mock services simple to avoid unnecessary complexity.
Use $provide only inside beforeEach(module()) to ensure proper injection.
Clean up dependencies after each test using afterEach().
Mock constants when testing different environments.
Prefer $provide.value() for simple objects and $provide.service() for methods.


8. Running Tests

Execute tests using:

karma start

For continuous testing:

karma start --auto-watch

Leave a Reply

Your email address will not be published. Required fields are marked *