Challenge #10: Set the Response
There’s one hardcoded value still remaining: the spy server’s response. In this challenge, you’ll add a method for setting the response.
Instructions
- Add a
spyServer.setResponse()method with the following signature:spyServer.setResponse({ status, // the status to use for the next server response header, // the headers to use for the next server response body, // the body to use for the next server response } - Modify
spyServer.reset()to callsetResponsewith the following defaults:spyServer.setResponse({ status: 501, // not implemented header: {}, body: "SpyServer response not specified", }); - Modify the spy server’s response to use the values set in
spyServer.setResponse(). The test should fail. - Update the test to call
spyServer.setResponse()as follows. The test should pass.spyServer.setResponse({ status: 999, headers: { myResponseHeader: "myResponseValue", }, body: "my response body" });
Remember to commit your changes when you’re done.
API Documentation
Object.entries(object).forEach(fn);
Run fn() on every key/value pair in object. The function parameter is a [ key, value ] array. Use it with array destructuring like this:
const myObject = {
a: "one",
b: "two",
};
Object.entries(myObject).forEach(([ key, value ] => {
console.log(`${key} --> ${value}`);
});
// outputs:
// a --> one
// b --> two
- fn
(([ key, value ]) => void)- the function to call on each key/value pair
JavaScript Primers
Hints
1
Start by introducing the spyServer.setResponse() method.
Store the response in an instance variable for later.
class SpyServer {
constructor() {
this.reset();
}
reset() {
this.lastRequest = null;
}
setResponse(response) {
this._response = response;
}
// ...
}
TypeScript requires type declarations and an interface:
interface SpyServerResponse {
status: number,
headers: HttpHeaders,
body: string,
}
class SpyServer {
_server?: http.Server;
_response?: SpyServerResponse;
lastRequest!: SpyServerRequest | null;
constructor() {
this.reset();
}
reset() {
this.lastRequest = null;
}
setResponse(response: SpyServerResponse) {
this._response = response;
}
// ...
}
2
Update spyServer.reset() to call setResponse().
reset() {
this.lastRequest = null;
this.setResponse({
status: 501,
headers: {},
body: "SpyServer response not specified",
});
}
In TypeScript, because reset() is called from the constructor, this allows you to declare that the _response variable will never be undefined:
class SpyServer {
_server?: http.Server;
_response!: SpyServerResponse;
lastRequest!: SpyServerRequest | null;
// ...
}
3
Modify the spy server to use the values from setResponse().
The headers are a bit more complicated, so do the status and body first.
The code to change is in the serverRequest.on("end", ...) event handler.
async startAsync() {
this._server = http.createServer();
await new Promise((resolve, reject) => {
this._server.listen(PORT);
this._server.on("listening", () => resolve());
});
this._server.on("request", (serverRequest, serverResponse) => {
let body = "";
serverRequest.on("data", (chunk) => {
body += chunk;
});
serverRequest.on("end", () => {
this.lastRequest = {
method: serverRequest.method,
path: serverRequest.url,
headers: serverRequest.headers,
body,
}; // add 'as SpyServerRequest' in TypeScript
serverResponse.statusCode = this._response.status;
serverResponse.setHeader("myResponseHeader", "myResponseValue");
serverResponse.end(this._response.body);
});
});
}
4
The test should fail.
That’s because the test isn’t setting a response. You’ll get to it when you’re done modifying the spy server.
5
Modify the spy server to use the headers from setResponse().
You’ll need to loop over all the headers and call serverResponse.setHeader() for each one.
You can use Object.entries().forEach() for that.
async startAsync() {
this._server = http.createServer();
await new Promise((resolve, reject) => {
this._server.listen(PORT);
this._server.on("listening", () => resolve());
});
this._server.on("request", (serverRequest, serverResponse) => {
let body = "";
serverRequest.on("data", (chunk) => {
body += chunk;
});
serverRequest.on("end", () => {
this.lastRequest = {
method: serverRequest.method,
path: serverRequest.url,
headers: serverRequest.headers,
body,
}; // add 'as SpyServerRequest' in TypeScript
serverResponse.statusCode = this._response.status;
Object.entries(this._response.headers).forEach(([ key, value ]) => {
serverResponse.setHeader(key, value);
});
serverResponse.end(this._response.body);
});
});
}
(The forEach() arrow function uses array destructuring.)
6
You’re ready to fix the test.
All you have to do is call spyServer.setResponse().
You need to set the response before making the request.
it("performs request", async () => {
spyServer.setResponse({
status: 999,
headers: {
myResponseHeader: "myResponseValue",
},
body: "my response body"
});
const clientRequest = http.request({
host: HOST,
port: PORT,
method: "POST",
path: "/my/path",
headers: {
myRequestHeader: "myRequestValue",
},
});
clientRequest.end("my request body");
const response = await new Promise((resolve, reject) => { // add <HttpClientResponse> in TypeScript
clientRequest.on("response", (clientResponse) => {
let body = "";
clientResponse.on("data", (chunk) => {
body += chunk;
});
clientResponse.on("end", () => {
resolve({
status: clientResponse.statusCode, // add 'as number' in TypeScript
headers: clientResponse.headers, // add 'as HttpHeaders' in TypeScript
body,
});
});
});
});
assert.deepEqual(spyServer.lastRequest, {
method: "POST",
path: "/my/path",
headers: {
myrequestheader: "myRequestValue",
host: `${HOST}:${PORT}`,
connection: "close",
"content-length": "15",
},
body: "my request body",
});
delete response.headers.date;
assert.deepEqual(response, {
status: 999,
headers: {
myresponseheader: "myResponseValue",
connection: "close",
"content-length": "16",
},
body: "my response body",
});
});
Complete Solution
Test code (JavaScript):
it("performs request", async () => {
spyServer.setResponse({
status: 999,
headers: {
myResponseHeader: "myResponseValue",
},
body: "my response body"
});
const clientRequest = http.request({
host: HOST,
port: PORT,
method: "POST",
path: "/my/path",
headers: {
myRequestHeader: "myRequestValue",
},
});
clientRequest.end("my request body");
const response = await new Promise((resolve, reject) => {
clientRequest.on("response", (clientResponse) => {
let body = "";
clientResponse.on("data", (chunk) => {
body += chunk;
});
clientResponse.on("end", () => {
resolve({
status: clientResponse.statusCode,
headers: clientResponse.headers,
body,
});
});
});
});
assert.deepEqual(spyServer.lastRequest, {
method: "POST",
path: "/my/path",
headers: {
myrequestheader: "myRequestValue",
host: `${HOST}:${PORT}`,
connection: "close",
"content-length": "15",
},
body: "my request body",
});
delete response.headers.date;
assert.deepEqual(response, {
status: 999,
headers: {
myresponseheader: "myResponseValue",
connection: "close",
"content-length": "16",
},
body: "my response body",
});
});
// ...
class SpyServer {
constructor() {
this.reset();
}
reset() {
this.lastRequest = null;
this.setResponse({
status: 501,
headers: {},
body: "SpyServer response not specified",
});
}
setResponse(response) {
this._response = response;
}
async startAsync() {
this._server = http.createServer();
await new Promise((resolve, reject) => {
this._server.listen(PORT);
this._server.on("listening", () => resolve());
});
this._server.on("request", (serverRequest, serverResponse) => {
let body = "";
serverRequest.on("data", (chunk) => {
body += chunk;
});
serverRequest.on("end", () => {
this.lastRequest = {
method: serverRequest.method,
path: serverRequest.url,
headers: serverRequest.headers,
body,
};
serverResponse.statusCode = this._response.status;
Object.entries(this._response.headers).forEach(([ key, value ]) => {
serverResponse.setHeader(key, value);
});
serverResponse.end(this._response.body);
});
});
}
async stopAsync() {
await new Promise((resolve, reject) => {
this._server.close();
this._server.on("close", () => resolve());
});
}
}
Test code (TypeScript):
it("performs request", async () => {
spyServer.setResponse({
status: 999,
headers: {
myResponseHeader: "myResponseValue",
},
body: "my response body"
});
const clientRequest = http.request({
host: HOST,
port: PORT,
method: "POST",
path: "/my/path",
headers: {
myRequestHeader: "myRequestValue",
},
});
clientRequest.end("my request body");
const response = await new Promise<HttpClientResponse>((resolve, reject) => {
clientRequest.on("response", (clientResponse) => {
let body = "";
clientResponse.on("data", (chunk) => {
body += chunk;
});
clientResponse.on("end", () => {
resolve({
status: clientResponse.statusCode as number,
headers: clientResponse.headers as HttpHeaders,
body,
});
});
});
});
assert.deepEqual(spyServer.lastRequest, {
method: "POST",
path: "/my/path",
headers: {
myrequestheader: "myRequestValue",
host: `${HOST}:${PORT}`,
connection: "close",
"content-length": "15",
},
body: "my request body",
});
delete response.headers.date;
assert.deepEqual(response, {
status: 999,
headers: {
myresponseheader: "myResponseValue",
connection: "close",
"content-length": "16",
},
body: "my response body",
});
});
// ...
interface SpyServerRequest {
method: string,
path: string,
headers: NodeIncomingHttpHeaders,
body: string,
}
interface SpyServerResponse {
status: number,
headers: HttpHeaders,
body: string,
}
class SpyServer {
_server?: http.Server;
_response!: SpyServerResponse;
lastRequest!: SpyServerRequest | null;
constructor() {
this.reset();
}
reset() {
this.lastRequest = null;
this.setResponse({
status: 501,
headers: {},
body: "SpyServer response not specified",
});
}
setResponse(response: SpyServerResponse) {
this._response = response;
}
async startAsync() {
await new Promise<void>((resolve, reject) => {
this._server = http.createServer();
this._server.listen(PORT);
this._server.on("listening", () => resolve());
this._server.on("request", (serverRequest, serverResponse) => {
let body = "";
serverRequest.on("data", (chunk) => {
body += chunk;
});
serverRequest.on("end", () => {
this.lastRequest = {
method: serverRequest.method,
path: serverRequest.url,
headers: serverRequest.headers,
body,
} as SpyServerRequest;
serverResponse.statusCode = this._response.status;
Object.entries(this._response.headers).forEach(([ key, value ]) => {
serverResponse.setHeader(key, value);
});
serverResponse.end(this._response.body);
});
});
});
}
async stopAsync() {
await new Promise<void>((resolve, reject) => {
if (this._server === undefined) return reject(new Error("SpyServer has not been started"));
this._server.close();
this._server.on("close", () => resolve());
});
}
}
No production code yet.

