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 callsetResponse
with 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.