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