Challenge #5: Nullability
You’ve seen how the createNull()
factory method allows you to instantiate real classes that have their communication with the outside world turned off. This is the Nullables pattern, and in this challenge, you’ll implement it by delegating to a Nullable HttpClient
. That’s called Fake It Once You Make It.
Instructions
1. Implement the "provides default response"
test (near the bottom):
- Modify the
transformAsync()
helper to takerot13Client
as an optional parameter. - Run
transformAsync()
on aRot13Client.createNull()
instance. - Assert that the
transformAsync()
response is"Nulled Rot13Client response"
.
2. Implement Rot13Client.createNull()
:
- Delegate to
HttpClient.createNull()
. - Simulate the following HTTP response:
{ status: 200, headers: { "content-type": "application/json", }, body: JSON.stringify({ transformed: "Nulled Rot13Client response", }), }
Remember to commit your changes when you’re done.
API Documentation
const rot13Client = Rot13Client.createNull();
Create a Nulled Rot13Client
.
- returns rot13Client
(Rot13Client)
- the ROT-13 client
JavaScript Primers
Hints
1
Your transformAsync()
test helper needs to be able to take a rot13Client
parameter so you can pass in the Nulled rot13Client
.
You can’t provide a default value in the parameter list because new Rot13Client()
needs a parameter that’s created later.
Instead, you’ll need to check if the rot13Client
is defined, and provide a default value if it isn’t.
The nullish coalescing operator (??
) is useful for this purpose.
async function transformAsync({
rot13Client,
port = IRRELEVANT_PORT,
text = IRRELEVANT_TEXT,
correlationId = IRRELEVANT_CORRELATION_ID,
rot13ServiceStatus = VALID_ROT13_STATUS,
rot13ServiceHeaders = VALID_ROT13_HEADERS,
rot13ServiceBody = VALID_ROT13_BODY,
}) {
const httpClient = HttpClient.createNull({
"/rot13/transform": {
status: rot13ServiceStatus,
headers: rot13ServiceHeaders,
body: rot13ServiceBody,
},
});
const httpRequests = httpClient.trackRequests();
rot13Client = rot13Client ?? new Rot13Client(httpClient);
const rot13Requests = rot13Client.trackRequests();
const response = await rot13Client.transformAsync(port, text, correlationId);
return { response, rot13Requests, httpRequests };
}
In TypeScript, because rot13Client
doesn’t have a default, you’ll have to declare types again. Define an interface to make the code easier to read:
interface TransformOptions {
rot13Client?: Rot13Client,
port?: number,
text?: string,
correlationId?: string,
rot13ServiceStatus?: number,
rot13ServiceHeaders?: HttpHeaders,
rot13ServiceBody?: string,
}
async function transformAsync({
rot13Client,
port = IRRELEVANT_PORT,
text = IRRELEVANT_TEXT,
correlationId = IRRELEVANT_CORRELATION_ID,
rot13ServiceStatus = VALID_ROT13_STATUS,
rot13ServiceHeaders = VALID_ROT13_HEADERS,
rot13ServiceBody = VALID_ROT13_BODY,
}: TransformOptions) {
const httpClient = HttpClient.createNull({
"/rot13/transform": {
status: rot13ServiceStatus,
headers: rot13ServiceHeaders,
body: rot13ServiceBody,
},
});
const httpRequests = httpClient.trackRequests();
rot13Client = rot13Client ?? new Rot13Client(httpClient);
const rot13Requests = rot13Client.trackRequests();
const response = await rot13Client.transformAsync(port, text, correlationId);
return { response, rot13Requests, httpRequests };
}
2
You’re ready to write the test.
First, create the Nulled Rot13Client
.
You can do this with Rot13Client.createNull()
.
Next, run transformAsync()
. Don’t forget to await
it.
Finally, assert that you got the expected response.
it("provides default response", async () => {
const rot13Client = Rot13Client.createNull();
const { response } = await transformAsync({ rot13Client });
assert.equal(response, "Nulled Rot13Client response");
});
3
The test is ready to run.
It should fail with a not implemented
error.
This is because the production code has a placeholder that’s throwing that error.
4
Your production code needs to instantiate Rot13Client
.
You can use new Rot13Client(httpClient)
to do that, but it needs an HttpClient
instance.
HttpClient
needs to be Nulled, so it doesn’t talk to the outside world, and it should be configured to return the same response a real ROT-13 service would.
You can use HttpClient.createNull()
to do that. The parameters are similar to the ones in the transformAsync()
test helper.
Rather than hardcoding the endpoint, use the TRANSFORM_ENDPOINT
constant. (You’ll have to use a computed property name.)
Although only the response body is required, fill in the status and headers with real-world values, too. This ensures your tests encounter real production behaviors.
static createNull(options) {
const httpClient = HttpClient.createNull({
[TRANSFORM_ENDPOINT]: [{
status: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
transformed: "Nulled Rot13Client response",
}),
}],
});
return new Rot13Client(httpClient);
}
Complete Solution
Test code (JavaScript):
it("provides default response", async () => {
const rot13Client = Rot13Client.createNull();
const { response } = await transformAsync({ rot13Client });
assert.equal(response, "Nulled Rot13Client response");
});
// ...
async function transformAsync({
rot13Client,
port = IRRELEVANT_PORT,
text = IRRELEVANT_TEXT,
correlationId = IRRELEVANT_CORRELATION_ID,
rot13ServiceStatus = VALID_ROT13_STATUS,
rot13ServiceHeaders = VALID_ROT13_HEADERS,
rot13ServiceBody = VALID_ROT13_BODY,
}) {
const httpClient = HttpClient.createNull({
"/rot13/transform": {
status: rot13ServiceStatus,
headers: rot13ServiceHeaders,
body: rot13ServiceBody,
},
});
const httpRequests = httpClient.trackRequests();
rot13Client = rot13Client ?? new Rot13Client(httpClient);
const rot13Requests = rot13Client.trackRequests();
const response = await rot13Client.transformAsync(port, text, correlationId);
return { response, rot13Requests, httpRequests };
}
Test code (TypeScript):
it("provides default response", async () => {
const rot13Client = Rot13Client.createNull();
const { response } = await transformAsync({ rot13Client });
assert.equal(response, "Nulled Rot13Client response");
});
// ...
interface TransformOptions {
rot13Client?: Rot13Client,
port?: number,
text?: string,
correlationId?: string,
rot13ServiceStatus?: number,
rot13ServiceHeaders?: HttpHeaders,
rot13ServiceBody?: string,
}
async function transformAsync({
rot13Client,
port = IRRELEVANT_PORT,
text = IRRELEVANT_TEXT,
correlationId = IRRELEVANT_CORRELATION_ID,
rot13ServiceStatus = VALID_ROT13_STATUS,
rot13ServiceHeaders = VALID_ROT13_HEADERS,
rot13ServiceBody = VALID_ROT13_BODY,
}: TransformOptions) {
const httpClient = HttpClient.createNull({
"/rot13/transform": {
status: rot13ServiceStatus,
headers: rot13ServiceHeaders,
body: rot13ServiceBody,
},
});
const httpRequests = httpClient.trackRequests();
rot13Client = rot13Client ?? new Rot13Client(httpClient);
const rot13Requests = rot13Client.trackRequests();
const response = await rot13Client.transformAsync(port, text, correlationId);
return { response, rot13Requests, httpRequests };
}
Production code (JavaScript):
static createNull(options) {
const httpClient = HttpClient.createNull({
[TRANSFORM_ENDPOINT]: [{
status: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
transformed: "Nulled Rot13Client response",
}),
}],
});
return new Rot13Client(httpClient);
}
Production code (TypeScript):
static createNull(options?: NulledRot13ClientResponses): Rot13Client {
const httpClient = HttpClient.createNull({
[TRANSFORM_ENDPOINT]: [{
status: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
transformed: "Nulled Rot13Client response",
}),
}],
});
return new Rot13Client(httpClient);
}