![]()
ngResource is a powerful module in AngularJS for interacting with RESTful APIs. However, when writing unit tests with Jasmine, mocking API calls using $httpBackend often fails due to incorrect setup or misunderstandings of $resource behavior.
This guide covers common reasons why mocking ngResource API calls fails and how to fix them.
1. Understanding ngResource and $httpBackend
In AngularJS, $resource is a wrapper around $http that provides an easier way to interact with REST APIs.
Example of an AngularJS service using $resource:
angular.module('myApp').factory('UserService', function($resource) {
return $resource('/api/users/:id', { id: '@id' }, {
update: { method: 'PUT' }
});
});
Key Points:
.get({id: 1})→ PerformsGET /api/users/1.save(userData)→ PerformsPOST /api/users.update({id: 1}, userData)→ PerformsPUT /api/users/1.delete({id: 1})→ PerformsDELETE /api/users/1
When testing, we mock these API calls using $httpBackend.
2. Common Mistakes and Fixes in Mocking API Calls
Mistake 1: Forgetting to Inject $httpBackend
Incorrect:
describe('UserService Tests', function() {
var UserService;
beforeEach(module('myApp'));
beforeEach(inject(function(_UserService_) {
UserService = _UserService_;
}));
it('should fetch a user', function() {
UserService.get({ id: 1 }).$promise.then(function(response) {
expect(response.name).toBe('John'); // No API call is intercepted!
});
});
});
Problem:
$httpBackendis missing, so Angular tries to make a real API call.
Fix (Inject $httpBackend):
describe('UserService Tests', function() {
var UserService, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function(_UserService_, _$httpBackend_) {
UserService = _UserService_;
$httpBackend = _$httpBackend_;
}));
it('should fetch a user', function() {
$httpBackend.expectGET('/api/users/1').respond(200, { name: 'John' });
UserService.get({ id: 1 }).$promise.then(function(response) {
expect(response.name).toBe('John');
});
$httpBackend.flush(); // Forces response to be processed
});
});
What this fixes:
- Ensures
$httpBackendintercepts the API request. flush()ensures the mock response resolves before expectations run.
Mistake 2: Using .then() on a Resource Object Instead of $promise
Incorrect:
it('should fetch a user', function() {
$httpBackend.expectGET('/api/users/1').respond(200, { name: 'John' });
var user = UserService.get({ id: 1 });
user.then(function(response) { // TypeError: user.then is not a function
expect(response.name).toBe('John');
});
$httpBackend.flush();
});
Problem:
$resource.get()returns an object, not a promise.- Solution: Use
.get().$promise.then()
Fix:
it('should fetch a user', function() {
$httpBackend.expectGET('/api/users/1').respond(200, { name: 'John' });
UserService.get({ id: 1 }).$promise.then(function(response) {
expect(response.name).toBe('John');
});
$httpBackend.flush();
});
Mistake 3: Not Using $httpBackend.expect... Before Making the Request
Incorrect:
it('should create a new user', function() {
var userData = { name: 'Alice' };
UserService.save(userData).$promise.then(function(response) {
expect(response.id).toBe(100); // Response is undefined
});
$httpBackend.expectPOST('/api/users').respond(200, { id: 100 });
$httpBackend.flush();
});
Problem:
- The request is made before
$httpBackend.expectPOST()is set up. - Solution: Declare expectations before calling the service.
Fix:
it('should create a new user', function() {
var userData = { name: 'Alice' };
$httpBackend.expectPOST('/api/users').respond(200, { id: 100 });
UserService.save(userData).$promise.then(function(response) {
expect(response.id).toBe(100);
});
$httpBackend.flush();
});
Mistake 4: Forgetting $httpBackend.verifyNoOutstandingExpectation() and $httpBackend.verifyNoOutstandingRequest()
Incorrect:
afterEach(function() {
$httpBackend.flush(); // May cause "No pending request to flush" error
});
Problem:
flush()may fail if there are no pending requests.
Fix:
jsCopyEditafterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Why this works:
- Ensures all expected HTTP requests were made.
- Prevents leftover requests from interfering with other tests.
Mistake 5: Not Handling HTTP Errors Properly
Incorrect:
it('should handle a failed API call', function() {
$httpBackend.expectGET('/api/users/1').respond(500, { error: 'Server error' });
UserService.get({ id: 1 }).$promise.then(function(response) {
// This will never run on a failed request
expect(response.name).toBeDefined();
});
$httpBackend.flush();
});
Problem:
.then()runs only on successful responses.- Solution: Use
.catch()to handle errors.
Fix:
it('should handle a failed API call', function() {
$httpBackend.expectGET('/api/users/1').respond(500, { error: 'Server error' });
UserService.get({ id: 1 }).$promise.catch(function(error) {
expect(error.data.error).toBe('Server error');
});
$httpBackend.flush();
});
Summary: Fixing ngResource Mocking Issues
| Issue | Fix |
|---|---|
$httpBackend missing | Inject $httpBackend in beforeEach() |
.then() not working | Use .get().$promise.then() |
API call before expect... | Declare $httpBackend.expectGET() before making requests |
flush() error in afterEach() | Use verifyNoOutstandingExpectation() and verifyNoOutstandingRequest() |
| Failing API calls not handled | Use .catch() |
