When writing unit tests in Jasmine for an AngularJS application, one of the most common issues is mocking services not working correctly. This can cause errors such as:
"TypeError: myService.getData is not a function"
"Cannot read property 'value' of undefined"
"Unexpected HTTP request"
This guide will explain why service mocking fails, how to correctly mock services, and debugging techniques to fix common issues.
1. Common Reasons for Mocking Failure
1.1. Forgetting to Include the Module
Before injecting a service, you must first load the AngularJS module:
beforeEach(module('myApp'));
If this is missing, your test won’t recognize the service, leading to undefined errors.
1.2. Incorrect Dependency Injection
Ensure you’re correctly injecting the service inside beforeEach
:
beforeEach(inject(function (_myService_) {
myService = _myService_;
}));
If the injected name is incorrect (e.g., myService
instead of _myService_
), Jasmine won’t recognize the mock.
1.3. Not Providing a Mock Implementation
If a service is expected to return data but lacks a mock implementation, tests will fail.
Incorrect Mock:
beforeEach(module(function ($provide) {
$provide.value('myService', {}); // No methods defined
}));
Fixed Mock:
beforeEach(module(function ($provide) {
$provide.value('myService', {
getData: function () {
return { value: 100 };
}
});
}));
1.4. Forgetting $httpBackend.flush()
for Asynchronous Calls
When a service makes an HTTP call, but the test does not flush the request, it remains unresolved.
Incorrect:
it('should get data from API', function () {
myService.getData().then(function (response) {
expect(response.data.value).toBe(100); // ❌ Fails due to unresolved promise
});
});
Fixed:
it('should get data from API', function () {
$httpBackend.whenGET('/api/data').respond(200, { value: 100 });
myService.getData().then(function (response) {
expect(response.data.value).toBe(100);
});
$httpBackend.flush(); // Resolves the HTTP call
});
2. How to Properly Mock Services in Jasmine Tests
Example: Mocking a Simple Service
Suppose we have an AngularJS service:
app.service('myService', function () {
this.getData = function () {
return { value: 100 };
};
});
Correct Jasmine Test Case:
describe('MyService Tests', function () {
var myService;
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_) {
myService = _myService_;
}));
it('should return the correct value', function () {
expect(myService.getData().value).toBe(100);
});
});
Example: Mocking a Service with HTTP Calls
If myService
makes an HTTP request:
app.service('myService', function ($http) {
this.getData = function () {
return $http.get('/api/data');
};
});
Mocking this service in Jasmine:
describe('MyService API Call Test', function () {
var myService, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, _$httpBackend_) {
myService = _myService_;
$httpBackend = _$httpBackend_;
}));
it('should return data from the API', function () {
$httpBackend.whenGET('/api/data').respond(200, { value: 100 });
myService.getData().then(function (response) {
expect(response.data.value).toBe(100);
});
$httpBackend.flush(); // Required for async calls
});
});
Example: Using $q.defer()
to Mock Promises
If a service returns a promise but isn’t working in the test, manually mock it using $q.defer()
:
Original Service Using $q
app.service('myService', function ($q) {
this.getData = function () {
var deferred = $q.defer();
deferred.resolve({ value: 100 });
return deferred.promise;
};
});
Jasmine Test Case Using $q.defer()
describe('MyService Promise Test', function () {
var myService, $rootScope, deferred;
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, _$q_, _$rootScope_) {
myService = _myService_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
spyOn(myService, 'getData').and.returnValue(deferred.promise);
}));
it('should return data from the promise', function () {
var result;
myService.getData().then(function (data) {
result = data;
});
deferred.resolve({ value: 100 });
$rootScope.$apply(); // Triggers digest cycle
expect(result.value).toBe(100);
});
});
3. Debugging Techniques
1. Log the Mock Service Before Using It
console.log(myService); // Check if it’s undefined
console.log(myService.getData); // Check if function exists
2. Ensure the Module is Loaded
Check if the module is properly loaded:
angular.module('myApp'); // Throws an error if module is missing
3. Check if Mock Service is Injected
Use:
beforeEach(inject(function (_myService_) {
console.log(_myService_); // Should not be undefined
}));
✅ 4. Test If spyOn
is Working
jsCopyEditexpect(myService.getData).toBeDefined(); // Check if spy exists
expect(myService.getData).toHaveBeenCalled(); // Verify function was called
4. Summary of Fixes & Best Practices
Problem | Solution |
---|---|
Module not included | Use beforeEach(module('myApp')); |
Service not injected correctly | Ensure _myService_ is properly assigned |
HTTP requests not resolved | Use $httpBackend.flush(); |
Mock service missing methods | Define methods inside $provide.value() |
$q promise not resolving | Use $rootScope.$apply(); |
spyOn not working | Ensure service method exists before spying |