Congratulations!

Congratulations! You’ve finished the scripted challenges for the “Narrow Integration Tests” module.

Return to module overview

Bonus Challenges

If you’d like more challenges, here are some more you can try. They start easy but get difficult quickly.

Fail Fast on GET

Node.js won’t send a request body with a GET request. This is an easy thing to overlook, especially in tests, and can lead to a lot of wasted time debugging.

To prevent the problem, implement the "fails fast if body is provided with GET request" test and make it pass. Test that httpClient.requestAsync() throws an exception when the method is GET and the body isn’t empty.

This method will help you assert that an exception has been thrown:

await assert.throwsAsync(fnAsync, expectedError);

Assert that an asynchronous function throws an error. Use it like this:

await assert.throwsAsync(
	() => transformAsync(...),    // note: NO await here
	"my expected error",
);
  • fnAsync (function) - the function to run
  • expectedError (string | regex) - the error fnAsync is expected to throw

Throw Exception when Connection Refused

The code doesn’t yet handle errors. To fix this, implement the "fails gracefully if connection is refused" test and make it pass. The test should make a request to a port that doesn’t exist, then assert that the error contained ECONNREFUSED, like this:

await assert.throwsAsync(
	() => requestAsync({
		port: PORT + 1,      // connection should fail because no server is established at that port
	}),
	/ECONNREFUSED/,
);

Your production code will need to call this method:

clientRequest.on("error", (err) => { ... });  // handle errors

Refactor to request()

The httpClient.requestAsync() method is simplified for the purpose of this exercise. The real production code uses a method called request() that returns the following object:

{
	responsePromise,  // a promise with the HTTP result (status, headers, and body)
	cancelFn,         // a function that cancels the request
}

Refactor httpClient.requestAsync() to httpClient.request() and return the { responsePromise } object. Don’t return cancelFn yet.

Request Cancellation

Implement the "can cancel requests" test and make it pass. This code should cause the request to be cancelled:

const { responsePromise, cancelFn } = httpClient.request(...);
cancelFn();

This is particularly challenging! This is the test code:

it("can cancel requests", async () => {
	spyServer.setResponse({ hang: true });            // tell SpyServer not to return a response (prevents race condition)

	const { responsePromise, cancelFn } = request();  // make request using a non-asynchronous helper method

	const cancelled = cancelFn("my cancel message");  // cancel the request

	assert.equal(cancelled, true, "cancellation should succeed");   // check that cancelFn() returned true
	await assert.throwsAsync(
		() => responsePromise,                          // check that the responsePromise rejects
		"my cancel message",                            // check that the exception includes the cancelFn() message
		"request should throw exception",
	);
});

Your production code will need to call this method:

clientRequest.destroy(new Error(message));    // cancel request

Ignore Invalid Request Cancellation

Implement the "ignores additional requests to cancel" test. This test should check that a second call to cancelFn() returns false and does not change the exception message.

Next, implement the "ignores cancellation that occurs after response has been received" test. This test should check that calling cancelFn() after the response has been received returns false and does not result in an exception.

Solutions

To see the solutions to these bonus challenges, check out the javascript or typescript branch.

Return to module overview