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):

  1. Modify the transformAsync() helper to take rot13Client as an optional parameter.
  2. Run transformAsync() on a Rot13Client.createNull() instance.
  3. Assert that the transformAsync() response is "Nulled Rot13Client response".

2. Implement Rot13Client.createNull():

  1. Delegate to HttpClient.createNull().
  2. 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);
}

Next challenge

Return to module overview