How to Test Async Requests with MochaJS

In this article I show you how to test asynchronous requests with MochaJS.

To simplify the example I’m going to show you how to build a mock Web server. That way you can simulate the delay from a server, without actually having to run a server.

Step 1. Clone the starter project

Use git to clone my starter repo into a new folder.

git clone https://github.com/mitchallen/autom8able-mochajs-starter.git mocha-async-101

cd mocha-async-101/

npm install

Run the default tests to make sure the install went okay:

npm test

Step 2. Define a mock Web server

In this step I’m going to show you how to create a mock Web server.

I’ll show you how to use a timeout to simulate a delayed asynchronous response.

Replace the contents of src/index.js with this code and I will explain it below:

 // Define a mock Web server class

class MockServer {
  constructor() {
    this.timeout = 100;
    this.result = [
      { id: 'CT', state: 'Connecticut' },
      { id: 'ME', state: 'Maine' },
      { id: 'MA', state: 'Massachusetts' },
      { id: 'NH', state: 'New Hampshire' },
      { id: 'RI', state: 'Rhode Island' },
    ]
  }

  // Define a method to mock a Web server request

  request() {
    // TODO - will cover in next step
  }
}

// Define a factory function

function createMockServer() {
  return new MockServer();
}

// Export factory functions

module.exports = {
  createMockServer
}

The code above does the following:

  • Defines a mock Web server - a class that pretends it’s a Web server
  • Defines a method to mock a Web server request - this will be covered in the next step
  • Defines a factory function - a simplified way to return new objects that I’ve covered before
  • Exports a factory function - a way to export the factory function that I have also covered before

MockServer breakdown

The mock server has two properties whose defaults are set in the constructor:

  • timeout - the length of time to delay the async response (in milliseconds)
  • result - the mock result to return in JSON format

There is no option to pass the parameters during construction. Instead you can overwrite them after you create the object. For example:

var server = mockFactory.createMockServer();
// override defaults 
server.timeout = 1500;
server.result = { status: 200, message: 'Success!' };

Step 3. Fill in the request method

In the class defined above, fill in the method with this code:

request() {
  var _self = this;
  return new Promise( function (resolve, reject) {
    setTimeout( function() {
      resolve( _self.result )
    }, _self.timeout );
  });
}

The request method breakdown

The request method starts with this line:

var _self = this;

Within the Promise definition we need to refer to the two class properties (timeout and result). The problem is that if you use this.timeout, the this will refer to the Promise that it is encapsulated in. So the this that refers to the mock class has to be assigned to a different property.

return new Promise( function (resolve, reject) {
  setTimeout( function() {
    resolve( _self.result )
  }, _self.timeout );
});

The request method returns a new Promise.

The promise will resolve to the value of the mock server result property.

By wrapping the call in setTimeout we can control how long it takes for the Promise to resolve. The time is controlled by the mock server timeout property.

Step 4. Write some async tests

Replace the code in test/smoke-test.js with the code below:

"use strict";

// require assert
var assert = require('assert');

// require the factory module
const mockFactory = require('../src');

describe('smoke test', function() {
  context('createMockServer', function() {
    // factory method
    it('should return an object that is not null', function(done) {
      var server = mockFactory.createMockServer();
      assert.ok(server, 'server should not be null');
      done();
    });
    // mock server call
    it('should return default result', async function() {
      var server = mockFactory.createMockServer();
      const TEST_RESULT = server.result;
      const result = await server.request();
      assert.deepEqual(result, TEST_RESULT, 
        'results not what expected');
    });
  });
});

For async testing the focus of this article is the last test.

  • The test code is wrapped in an async function

When a function is marked as async in JavaScript, that means that it will be returning a Promise.

  • The factory method creates a new mock server (server)
  • For testing, the default result is saved to TEST_RESULT
  • The server.request() call is made using an await
  • Finally the assert.deepEqual method is used to verify that the server.request() call returned the default result
  • Notice that there is no done call

Step 5. Run the tests

npm test

Review the results:

smoke test
  createMockServer
    ✓ should return an object that is not null
    ✓ should return default result (100ms)

2 passing (109ms)

The second test includes the time it took the test to run (100ms). The default timeout value for the mock server is 100. Sometimes a test will run a little longer if it is doing more.

Step 6. Add a timing test

Having mocha log the time something took is useful. But sometimes you will want to automate a test for how long a procedure within your test took.

In this step I’m going to show you how to save the time before and after an async call is made. Then calculate and test the difference.

Add the test below:

it('should return after timeout', async function() {
  var server = mockFactory.createMockServer();
  const TEST_RESULT = server.result;
  const TEST_TIMEOUT = 1500;
  server.timeout = TEST_TIMEOUT;
  const start = new Date();
  const result = await server.request();
  const finish = new Date();
  const diff = finish - start;
  assert.ok( diff >= TEST_TIMEOUT, 'test returned before timeout' );
  assert.deepEqual(result, TEST_RESULT, 'results not what expected');
});

This test does the following:

  • Uses async because await will be called within the test function
  • Creates a new mock server (server) using the factory method (createMockServer)
  • Saves the expected response into TEST_RESULT for verifying later
  • Defines a new TEST_TIMEOUT constant (in milliseconds)
  • Overrides the default server.timeout with TEST_TIMEOUT
  • Saves the current date and time into start
  • Assigns the server.request to result
  • The await call will cause JavaScript to wait until the Promise returned by the request method either rejects or resolves
  • When the Promise returns, finish is assigned the current date and time
  • The diff value is assigned finish-start in milliseconds
  • The difference should be greater than or equal to the timeout
  • The returned result should equal what is defaut mock server result

Step 7. Run the updated tests

npm test

Review the results:

smoke test
  createMockServer
    ✓ should return an object that is not null
    ✓ should return default result (101ms)
    ✓ should return after timeout (1501ms)

3 passing (2s)

Notice that the new test took 1500ms or more.

Step 8. Testing with large timeouts

If a test takes more than 2 seconds (2000 milliseconds), by default mocha will time out.

Update the last test by changing the TEST_TIMEOUT to 2500 and save the file.

const TEST_TIMEOUT = 2500;

Run the tests again:

npm test

You will see a response like this:

  smoke test
    createMockServer
      ✓ should return an object that is not null
      ✓ should return default result (101ms)
      1) should return after timeout

  2 passing (2s)
  1 failing

  1) smoke test
       createMockServer
         should return after timeout:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, 
     ensure "done()" is called; if returning a Promise, 
     ensure it resolves. (/test/smoke-test.js)
      at listOnTimeout (internal/timers.js:549:17)
      at processTimers (internal/timers.js:492:7)

This is a problem when testing Web apps. Sometimes a server can take more than 2 seconds to respond.

You can fix the problem by setting the MochaJS timeout option either as a flag or as an option.

The quickest way to do that is to update the npm test script in package.json with a new flag.

Add a flag to make the test timeout 5000 milliseconds (5 seconds).

 "test": "mocha --timeout 5000"

Run the tests again and they should all pass because nothing timed out.

Step 9. Using mocha timeout

You can also override the timeout default and flag settings by setting the timeout for a test individually.

Add these two tests:

it('default timeout', function(done) {
  assert.ok(true);
  setTimeout(done, 5100);
});
it('override timeout', function(done) {
  this.timeout(6000);
  assert.ok(true);
  setTimeout(done, 5100);
});

The first one will take 5100ms - so it will go beyond the flag setting of 5000ms and fail with a timeout.

The second one overrides the flag timeout by setting the timeout to 6000ms and will pass.

This is useful when you only want to allow one specific test to take a long time.

If you run the tests again, the output will look like this:

> ./node_modules/mocha/bin/mocha --timeout 5000

  smoke test
    createMockServer
      ✓ should return an object that is not null
      ✓ should return default result (106ms)
      ✓ should return after timeout (2502ms)
      1) default timeout
      ✓ override timeout (5106ms)

  4 passing (13s)
  1 failing

  1) smoke test
       createMockServer
         default timeout:
     Error: Timeout of 5000ms exceeded. For async tests and hooks, 
     ensure "done()" is called; if returning a Promise, ensure it 
     resolves. 

The lengthy test that didn’t override the timeout failed, the other one passed.

Troubleshooting

If you run into weirdness when testing make sure that you aren’t using arrow functions. Especially when using this.timeout. MochaJS discourages using arrow function when defining tests.

Conclusion

In this article you learned how to do the following:

  • Build a mock Web server to simulate real server delays
  • Test requests asynchronously
  • Override the MochaJS default timeout setting

Here are some links to related articles that I’ve written:

References

  • Async/await [1]
  • MochaJS arrow functions [2]



About the Author

Mitch Allen tests cloud services for a robotics company in New England.