Challenge #1: Fake It Once You Make It
Rot13Client
’s purpose is to present a clean interface to the ROT-13 microservice. Under the covers, it sends an HTTP request to the microservice and parses the response. For this first challenge, you’ll use a low-level HttpClient
infrastructure wrapper to send the HTTP request.
This challenge introduces the Fake It Once You Make It pattern. Because HttpClient
is Nullable, your tests won’t make real HTTP requests. Instead, you’ll use a Nulled HttpClient
to pretend to make requests. This makes the tests much easier to write and understand.
Instructions
1. Implement the "makes request"
test:
- Call
rot13Client.transformAsync(9999, "text_to_transform", "my-correlation-id")
. - Assert that it makes the following HTTP request:
{ host: HOST, port: 9999, path: "/rot13/transform", method: "post", headers: { "content-type": "application/json", "x-correlation-id": "my-correlation-id", }, body: JSON.stringify({ text: "text_to_transform" }), }
2. Implement rot13Client.transformAsync()
:
- Make the HTTP request.
Remember to commit your changes when you’re done.
API Documentation
const rot13Client = new Rot13Client(httpClient);
Create a Rot13Client
. Most code will use one of the factory methods, but your test should call the constructor directly. It gives you more control over the class’s dependencies.
- httpClient
(HttpClient)
- the low-level HTTP client - returns rot13Client
(HomePageController)
- the ROT-13 client
const transformedText = await rot13Client.transformAsync(port, text, correlationId);
Call the ROT-13 service.
- port
(number)
- the port of the ROT-13 service - text
(string)
- the text to send to the ROT-13 service - correlationId
(string)
- a unique ID representing the user’s request - returns transformedText
(string)
- the encoded text returned by the ROT-13 service
const httpClient = HttpClient.createNull();
Create a Nulled HttpClient
.
- returns httpClient
(HttpClient)
- the HTTP client
const { status, headers, body } = await HttpClient.requestAsync({ host, port, method, path, headers, body });
Make an HTTP request. The request and response headers are objects, where the object names correspond to header names and the object values correspond to header values.
- host
(string)
- the hostname of the server - port
(number)
- the port of the server - method
(string)
- the HTTP method (GET
,POST
, etc.) to use - path
(string)
- the URL path to use - optional headers
(object)
- the HTTP headers to send; defaults to none - optional body
(string)
- the HTTP body to send; defaults to nothing - returns status
(number)
- the response status code - returns headers
(object)
- the response headers - returns body
(string)
- the response body
const httpRequests = httpClient.trackRequests();
Track requests made by httpClient
. This returns an OutputTracker
instance. Every time httpClient
makes an HTTP request, the OutputTracker
is updated with an object describing the request. The object is in this format:
{
host: "hostname",
port: 123,
path: "/the/url/path",
method: "post",
headers: {
"header-name1": "header value1",
"header-name2": "header value2",
},
body: "the body",
}
- returns httpRequests
(OutputTracker)
- the request tracker
const output = httpRequests.data;
Returns an array with every request stored in the output tracker. Use it like this:
const httpRequests = httpClient.trackRequests();
// run code that makes requests
const output = httpRequests.data;
- returns output
(array)
: the data
const json = JSON.stringify(variable)
;
Convert a variable to a JSON-encoded string.
- variable
(any)
- the object to convert - returns json
(string)
- the JSON string
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
const HOST = "localhost";
This constant is available in both the tests and production code. It’s the hostname of the server that has the ROT-13 service. Use it when you need to specify the service’s host.
const TRANSFORM_ENDPOINT = "/rot13/transform";
This constant is available in the production code. It’s the URL path of the ROT-13 service. Use it when you need to specify the service’s path.
TypeScript Types
httpRequests.data: HttpClientOutput[]
The requests stored in the output tracker.
interface HttpClientOutput extends HttpClientRequestParameters {
cancelled?: boolean, // not relevant to this challenge
}
interface HttpClientRequestParameters {
host: string,
port: number,
method: string,
path: string,
headers?: HttpHeaders,
body?: string,
}
JavaScript Primers
Hints
1
Your test will need a Rot13Client
.
You can construct it with new Rot13Client()
.
it("makes request", async () => {
// Arrange
const rot13Client = new Rot13Client(httpClient);
// Act
// Assert
});
2
The Rot13Client
constructor needs an HttpClient
instance.
You can use the HttpClient.createNull()
factory method to create it.
it("makes request", async () => {
// Arrange
const httpClient = HttpClient.createNull();
const rot13Client = new Rot13Client(httpClient);
// Act
// Assert
});
3
Your test will need to check whether the HTTP request was made.
You can track which requests are made by calling httpClient.trackRequests()
.
You have to call httpClient.trackRequests()
before the request is made.
it("makes request", async () => {
// Arrange
const httpClient = HttpClient.createNull();
const httpRequests = httpClient.trackRequests();
const rot13Client = new Rot13Client(httpClient);
// Act
// Assert
});
4
You need to run the code under test.
You can do that by calling rot13Client.transformAsync()
. Be sure to await
it.
it("makes request", async () => {
// Arrange
const httpClient = HttpClient.createNull();
const httpRequests = httpClient.trackRequests();
const rot13Client = new Rot13Client(httpClient);
// Act
await rot13Client.transformAsync(9999, "text_to_transform", "my-correlation-id");
// Assert
});
5
You’re ready to assert that the HTTP request was made.
You’re tracking HTTP requests in httpRequests
.
You can access the tracking data with httpRequests.data
.
You can use assert.deepEqual()
to compare httpRequests.data
to an array with the expected requests.
it("makes request", async () => {
// Arrange
const httpClient = HttpClient.createNull();
const httpRequests = httpClient.trackRequests();
const rot13Client = new Rot13Client(httpClient);
// Act
await rot13Client.transformAsync(9999, "text_to_transform", "my-correlation-id");
// Assert
assert.deepEqual(httpRequests.data, [{
host: HOST,
port: 9999,
path: "/rot13/transform",
method: "post",
headers: {
"content-type": "application/json",
"x-correlation-id": "my-correlation-id",
},
body: JSON.stringify({ text: "text_to_transform" }),
}]);
});
6
The test is complete. You can run it now.
It should fail, saying the actual value is []
(an empty array).
This means your production code isn’t making any HTTP requests.
7
Make the HTTP request in your production code.
You can use this._httpClient.requestAsync()
. Don’t forget to await
it.
async transformAsync(port, text, correlationId) {
await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: TRANSFORM_ENDPOINT,
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
}
(The port
parameter in the above solution is using object shorthand.)
In TypeScript, you’ll need to return a placeholder string:
async transformAsync(
port: number,
text: string,
correlationId: string,
): Promise<string> {
await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: TRANSFORM_ENDPOINT,
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
return "not implemented";
}
Complete Solution
Test code:
it("makes request", async () => {
// Arrange
const httpClient = HttpClient.createNull();
const httpRequests = httpClient.trackRequests();
const rot13Client = new Rot13Client(httpClient);
// Act
await rot13Client.transformAsync(9999, "text_to_transform", "my-correlation-id");
// Assert
assert.deepEqual(httpRequests.data, [{
host: HOST,
port: 9999,
path: "/rot13/transform",
method: "post",
headers: {
"content-type": "application/json",
"x-correlation-id": "my-correlation-id",
},
body: JSON.stringify({ text: "text_to_transform" }),
}]);
});
Production code (JavaScript):
async transformAsync(port, text, correlationId) {
await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: TRANSFORM_ENDPOINT,
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
}
Production code (TypeScript):
async transformAsync(
port: number,
text: string,
correlationId: string,
): Promise<string> {
await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: TRANSFORM_ENDPOINT,
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
return "not implemented";
}