Mocking ngResource API calls failing

Loading

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}) → Performs GET /api/users/1
  • .save(userData) → Performs POST /api/users
  • .update({id: 1}, userData) → Performs PUT /api/users/1
  • .delete({id: 1}) → Performs DELETE /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

IssueFix
$httpBackend missingInject $httpBackend in beforeEach()
.then() not workingUse .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 handledUse .catch()

Leave a Reply

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