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:
$httpBackend
is 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
$httpBackend
intercepts 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() |