1 Comment

Unit Testing Frontend Network Requests

Unit testing is a great way to help ensure the continuous delivery of working code over a product’s lifecycle. In client-side applications, unit testing data retrieval is especially important since using data from these asynchronous calls is at the core of what most of these apps do.

Unfortunately, asynchronous calls aren’t easy to test. Whatever tool you’re using to make your network requests (Maybe jQuery Ajax, Angular Resource, or Ember Data ), it’s almost certainly asynchronous. Asynchronous data loading is great for applications since you can display empty views and load other parts of the application while waiting for the data response to be there.

However, for unit testing, it’s more challenging.  Your app is depending on a network that doesn’t exist during tests, and the code using the data probably isn’t synchronous either.

What’s a developer to do? Don’t panic. There are several strategies for unit testing asynchronous network calls in your components.

Option 1: Use a Fake Server

Set up a fake server configured with the real API URL to respond with a test data fixture. In your test, you can synchronously wait for the mock server to be hit before making your assertion.

import sinon from 'sinon'; //http://sinonjs.org/
 
// jasmine test
describe('Fake server test', () => {
  let subject = {};
  it('does a server test', () => {
    let server = sinon.fakeServer.create();
 
    server.respondWith(
      'GET', '/something', 200, { 'Content-Type': 'application/json' },
                        { id: 42, name: 'The Answer' }]);
 
    subject = new Subject();
    server.respond(); // Process any pending requests
 
    expect(subject.get('something to test')).toBe(true);
 
    server.restore();
  });
});

Pros

  • The test simulates a complete round trip to and from the server.
  • It verifies that there are no errors before or after the data come back.

Cons

  • These tests depend on the API structure, but unit testing should isolate the item under test.
  • Setting up a fake server is verbose and requires a fair amount of both setup and cleanup.

Option 2: Directly Insert the Test Fixture Data

With this method, you inject the test data directly into the component under test, bypassing the asynchronous wait time.

// jasmine test
describe('Directly Inserted Data Test', () => {
  let subject = {};
  it('does a test', () => {
    subject.model = {id: 42, name: 'The Answer'};
 
    expect(subject.get('something to test')).toBe(true);
  });
});

Pros

  • These are fast tests that are isolated from the server and the API.
  • There’s no API dependency.
  • The isolated nature makes for a great unit test.

Cons

  • Test coverage is incomplete since the test does not verify that there are no errors while your component is waiting for data to arrive. (A common bug that will escape this test is trying to access data on an object that is undefined.)

Option 3: Use a Wrapper and Mock it

This is my favorite way to unit test network requests. Write a wrapper around the network calls that returns a promise of the network response data. Then, mock the wrapper. This is especially easy to do in ES6-style JavaScript since promises are a native type.

// jest test using jasmine-pit
describe('Mocked API Wrapper Test', () => {
  let subject = {};
 
  pit('does a mock wrapper', () => {
    let apiResponse = Promise.resolve({id: 42, name: 'The Answer'});
    ApiWrapper.getData.mockReturnValue(apiResponse);
 
    subject = new Subject();
 
    return apiResponse.then(() => expect(subject.get('something to test')).toBe(true));
  });
});

Pros

  • By mocking the wrapper with a promise, we can test the component both before and after the data is returned.
  • Because we are mocking a wrapper and not the server itself, we are not depending on the server’s exact API to conduct our small front-end unit test.
  • Setup is less verbose than setting up a fake server.

Cons

  • You need to be able to resolve test assertions asynchronously.

How to Resolve Test Assertions Asynchronously

Mocha and Jasmine Pit are tools that will allow you to return a promise from a test to resolve your assertions asynchronously. Currently, I’m using Jest for unit testing, which includes Jasmine Pit for doing asynchronous assertions. Jest mocks everything not under test by default, which makes it very simple to set up an effective test for an asynchronous call.

Most frontend unit tests should directly insert data into the test, then verify the transformations and behavior of that data. However, any component that is fetching data will benefit from at least one test that verifies its behavior before, during, and immediately after the asynchronous network request.

In my experience, the least painful way to effectively test this is to create a wrapper around my API access layer, and use promises to mock out my data response.

What are your favorite strategies for frontend unit tests?