Challenge #3: Default Response

The Nulled HttpClient needs to return simulated responses. In this challenge, you’ll modify the embedded stub to make it return a default response.

Instructions

1. In the "provides default response" test:

  1. Assert that the Nulled HttpClient returns a following response:
    {
    	status: 503,
    	headers: { nulledhttpclient: "default header" },
    	body: "Nulled HttpClient default body",
    }

2. In the production code:

  1. Modify the embedded stub to make the test pass.
  2. Use the DEFAULT_NULLED_RESPONSE constant.

Remember to commit your changes when you’re done.

Understanding HttpClient

clientResponse.on("data", fn);

Run fn when the client receives body data from the server. Note that the server can send multiple pieces of data, so fn can be called multiple times. Accumulate it into a string like this:

This is a replacement for clientResponse.resume()

let data = "";
clientResponse.on("data", (chunk) => {
	data += chunk;
}
  • fn ((chunk) => void) - the function to run
  • chunk (Buffer) - the data
const status = clientResponse.statusCode;

The response’s status code.

  • returns status (number) - the status code
const headers = clientResponse.headers;

The response’s headers. Header names and values are mapped to object names and values.

  • returns headers (object) - the headers

API Documentation

const { response } = await requestAsync({ client });

Available in the tests. A helper method for calling httpClient.requestAsync(). Its main benefit is that it provides defaults for parameters your test doesn’t care about.

  • optional client (HttpClient) - the HttpClient to use; defaults to HttpClient.create()
  • returns response ({ status, headers, body }) - the HTTP response
  • returns status (number) - the response status code
  • returns headers (object) - the response headers
  • returns body (string) - the response body
assert.deepEqual(actual, expected)

Assert that two objects and all their recursive properties are equal.

  • actual (any) - the actual value
  • expected (any) - the expected value

TypeScript Types

clientResponse.headers: HttpHeaders

The headers received from the server.

type HttpHeaders = Record<string, HttpHeader>;
type HttpHeader = string;

JavaScript Primers

Hints

1
Your test needs to call httpClient.request() and get the response.
You can use the requestAsync() test helper to do that. (You’ll need to destructure the response.)
You’ll need a Nulled HttpClient.
it("provides default response", async () => {
	const client = HttpClient.createNull();
	const { response } = await requestAsync({ client });
});
2
Your test needs to assert that the response is correct.
You can use assert.deepEqual to do that.
it("provides default response", async () => {
	const client = HttpClient.createNull();
	const { response } = await requestAsync({ client });

	assert.deepEqual(response, {
		status: 503,
		headers: { nulledhttpclient: "default header" },
		body: "Nulled HttpClient default body",
	});
});
3
You’re ready to run the test.
It should fail, complaining that the status, headers, and body are undefined. (In TypeScript, they might have placeholder values.)

That’s because StubbedHttp isn’t providing them.

4
Where should status, headers, and body be implemented?
Look at the production code. Where is they returned?
They’re returned by calling resolve() in the clientResponse.on("end", ...) event handler.
Where does the event handler get them from?
It gets them from clientResponse.
What is the equivalent stub code?
It’s StubbedResponse.

They should be implemented in StubbedResponse.

5
Implement status.
What does the clientResponse.on("end", ...) event handler do to get the status?
It reads the clientResponse.statusCode property.
You can set that property in the StubbedResponse constructor.
class StubbedResponse extends EventEmitter {

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;

		setImmediate(() => {
			this.emit("data");
			this.emit("end");
		});
	}

}

In TypeScript, remember to remove the placeholder:

class StubbedResponse extends EventEmitter implements NodeHttpResponse {

	statusCode: number;
	headers = {};

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;

		setImmediate(() => {
			this.emit("data");
			this.emit("end");
		});
	}

}
6
Implement headers.
It’s the same as status.
class StubbedResponse extends EventEmitter {

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;
		this.headers = DEFAULT_NULLED_RESPONSE.headers;

		setImmediate(() => {
			this.emit("data");
			this.emit("end");
		});
	}

}

TypeScript:

class StubbedResponse extends EventEmitter implements NodeHttpResponse {

	statusCode: number;
	headers: HttpHeaders;

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;
		this.headers = DEFAULT_NULLED_RESPONSE.headers;

		setImmediate(() => {
			this.emit("data");
			this.emit("end");
		});
	}

}
7
How should body be implemented?
What does the clientResponse.on("end", ...) event handler to get the body?
It’s generated by the clientResponse.on("data", ...) event handler.
Where does the clientResponse.on("data", ...) event handler get the body?
It’s received in one or more chunk parameters to the event handler.

The body should be implemented by passing it to the clientResponse.on("data", ...) event handler.

8
Implement body.
You can do that by modifying the call to this.emit("data").
class StubbedResponse extends EventEmitter {

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;
		this.headers = DEFAULT_NULLED_RESPONSE.headers;

		setImmediate(() => {
			this.emit("data", DEFAULT_NULLED_RESPONSE.body);
			this.emit("end");
		});
	}

}

Complete Solution

Test code:
it("provides default response", async () => {
	const client = HttpClient.createNull();
	const { response } = await requestAsync({ client });

	assert.deepEqual(response, {
		status: 503,
		headers: { nulledhttpclient: "default header" },
		body: "Nulled HttpClient default body",
	});
});
Production code (JavaScript):
class StubbedResponse extends EventEmitter {

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;
		this.headers = DEFAULT_NULLED_RESPONSE.headers;

		setImmediate(() => {
			this.emit("data", DEFAULT_NULLED_RESPONSE.body);
			this.emit("end");
		});
	}

}
Production code (TypeScript):
class StubbedResponse extends EventEmitter implements NodeHttpResponse {

	statusCode: number;
	headers: HttpHeaders;

	constructor() {
		super();

		this.statusCode = DEFAULT_NULLED_RESPONSE.status;
		this.headers = DEFAULT_NULLED_RESPONSE.headers;

		setImmediate(() => {
			this.emit("data", DEFAULT_NULLED_RESPONSE.body);
			this.emit("end");
		});
	}

}

Next challenge

Return to module overview