Simple Ajax testing

Verifying that a request to external service happens.

A lot of our code makes requests to external services. For example, a crash reporting client, like raven sends an exception and related information to Sentry. If we include raven client, how do we verify that it is setup and configured correctly, and actually sends the crash information? We do not want to find out that the system has been running red, yet without any errors getting through!

I typically use a zero-configuration wrapper around a crash reporter, like crash-reporter-middleware or node-sentry-error-reporter. These clients ensure that all errors are actually sent from a Node process, including rejected promises and exceptions from the server middleware. How do I make sure the error is actually sent?

Consider a small example where the client sends an error manually

1
2
3
// example.js
const reporter = require('node-sentry-error-reporter')()
reporter(new Error('Something went wrong'))

Somewhere deep inside the Node process a HTTP POST request is fired by the raven library. We usually do not want to actually make a request, and raven does not allow spying on the send. Thus we need a mock server to work as Sentry receiver to verify that the error is being sent.

Luckily, there are mock server libraries, like nock that make testing such requests a breeze. For example, if we want to confirm that a request is received, we can setup an expectation first, before the request happens

1
2
3
4
5
6
7
// mock-sentry.js
const nock = require('nock')
nock('http://localhost')
.post('/api/11/store/')
.reply(200, function (uri, body) {
console.log('received mock sentry request', uri)
})

The above assumes that we configure raven with the following SENTRY_URL=http://aaa:111@localhost/11. Here is our test code

Two details

To make this unit testing more flexible, we can do the following.

Use the full package name

Instead of loading the package under test using relative path, we should load it using full name

1
2
3
4
// BAD
const reporter = require('..')()
// GOOD
const reporter = require('node-sentry-error-reporter')()

To allow full module name, we should run the test with extra NODE_PATH folder

1
NODE_PATH=.. node example.js

which assumes the typical file structure

1
2
3
4
5
6
projects/
node-sentry-error-reporter/
node_modules/
package.json
example.js
...

By looking one folder up, the Node require will find node-sentry-error-reporter folder and will load just like any other NPM module.

Load mock server code separately

Instead of loading the mock server, and loading the test code from inside the mock code, use the external preload feature available in Node

1
2
3
4
5
6
7
8
9
10
11
12
// BAD
// mock-sentry.js
const nock = require('nock')
nock('http://localhost')
.post('/api/11/store/')
.reply(200, function (uri, body) {
console.log('received mock sentry request', uri)
})
require('./example.js')
// example.js
const reporter = require('node-sentry-error-reporter')()
reporter(new Error('Something went wrong'))
1
NODE_PATH=.. node mock-sentry.js

Here is a better alternative. mock-sentry.js will NOT know about any other file

1
2
3
4
5
6
7
8
9
10
11
// GOOD
// mock-sentry.js
const nock = require('nock')
nock('http://localhost')
.post('/api/11/store/')
.reply(200, function (uri, body) {
console.log('received mock sentry request', uri)
})
// example.js
const reporter = require('node-sentry-error-reporter')()
reporter(new Error('Something went wrong'))
1
NODE_PATH=.. node -r ./mock-sentry.js mock-sentry.js

The -r flag (--require) preloads a module (note the relative path) before running the file at the end of the command. You can even preload multiple modules by using the -r option multiple times. Other testing tools, like mocha allow using this flag too; thus setting up mock server responses in unit tests is super simple too.

You can see the testing code in the test folder and how it is called in the package.json.

Related