Mocking services not working in Jasmine tests

Loading

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

ProblemSolution
Module not includedUse beforeEach(module('myApp'));
Service not injected correctlyEnsure _myService_ is properly assigned
HTTP requests not resolvedUse $httpBackend.flush();
Mock service missing methodsDefine methods inside $provide.value()
$q promise not resolvingUse $rootScope.$apply();
spyOn not workingEnsure service method exists before spying

Leave a Reply

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