Challenge #6: Configurable Responses

Nullables have the ability to configure their responses. This is the Configurable Responses pattern, and in this challenge, you’ll implement it.

When Faking It Once You Make It, implementing Configurable Responses involves decomposing high-level responses, such as Rot13Client’s transformed text, into a low-level responses, such as HttpClient’s HTTP response. That’s what you’ll do in this challenge.

Instructions

1. Add support for configuring one response:

  1. Start with the "can configure multiple responses" test (near the bottom).
  2. Write a test to assert on one response and get it to pass. Use the following configuration (note the array):
    const rot13Client = Rot13Client.createNull([
    	{ response: "response 1" },
    ]);
  3. Modify Rot13Client.createNull() to get the test to pass.

2. Add support for configuring multiple responses:

  1. Modify the test to assert on two responses and get it to pass. Use the following configuration:
    const rot13Client = Rot13Client.createNull([
    	{ response: "response 1" },
    	{ response: "response 2" },
    ]);
  2. Modify Rot13Client.createNull() to get the test to pass.

Remember to commit your changes when you’re done.

API Documentation

const arrayOut = array.map(transformFn);

Convert an array into another array of the same size by running transformFn() on each element.

transformFn() is a function that takes an array element as a parameter and returns the new element. For example, the following code adds an exclamation mark to each array element:

const original = [ "a", "b", "c" ];

const transformed = original.map((element) => {
	return element + "!";
}

console.log(transformed);   // outputs [ "a!", "b!", "c!" ]
  • transformFn ((element) => elementOut) - the function to convert array elements
  • returns arrayOut (array) - the converted array

TypeScript Types

Rot13Client.createNull(responses: NulledRot13ClientResponses)

Rot13Client’s configurable responses.

type NulledRot13ClientResponses = NulledRot13ClientResponse[];

interface NulledRot13ClientResponse {
	response?: string,
	error?: string,
	hang?: boolean,
}
HttpClient.createNull(responses: NulledHttpClientResponses)

HttpClient’s configurable responses.

type NulledHttpClientResponses = Record<string, NulledHttpClientResponse | NulledHttpClientResponse[]>;

interface NulledHttpClientResponse {
	status?: number,
	headers?: HttpHeaders,
	body?: string,
	hang?: boolean,
}

type HttpHeaders = Record<string, HttpHeader>;

type HttpHeader = string;

JavaScript Primers

Hints

1
Test one response.
1. Create the rot13Client with Rot13Client.createNull().
2. Run the transformAsync() test helper. Don’t forget to await it.
3. Assert that the response is correct with assert.equal().
it("can configure multiple responses", async () => {
	const rot13Client = Rot13Client.createNull([
		{ response: "response 1" },
	]);

	const { response: response1 } = await transformAsync({ rot13Client });

	assert.equal(response1, "response 1");
});
2
The test is ready to run.
When you run it, it should get "Nulled Rot13Client response" instead of the "response 1" it expected.

That’s because Rot13Client.createNull() isn’t using the configured response.

3
In Rot13Client.createNull(), use the response in the options parameter.
You can use options[0].response to get the response.
static createNull(options) {
	const response = options[0].response;

	const httpClient = HttpClient.createNull({
		[TRANSFORM_ENDPOINT]: [{
			status: 200,
			headers: {
				"content-type": "application/json",
			},
			body: JSON.stringify({
				transformed: response,
			}),
		}],
	});
	return new Rot13Client(httpClient);
}

TypeScript will complain about possibly undefined values. You can fix it with the optional chaining operator (?.), which will propagate undefined into response:

static createNull(options?: NulledRot13ClientResponses): Rot13Client {
	const response = options?.[0]?.response;

	const httpClient = HttpClient.createNull({
		[TRANSFORM_ENDPOINT]: [{
			status: 200,
			headers: {
				"content-type": "application/json",
			},
			body: JSON.stringify({
				transformed: response,
			}),
		}],
	});
	return new Rot13Client(httpClient);
}
4
Try running the tests again.
They should fail, saying that undefined prevented the test from reading 0. That means options is undefined. (TypeScript will just say the result was undefined.)
It’s the "provides default response" test that’s failing, not the test you’re working on.

The test is failing because it’s not passing in any options.

5
Make the production code work when no options are provided.
Provide a default function parameter.
The default should be an array of objects.
static createNull(options = [{ response: "Nulled Rot13Client response" }]) {
	const response = options[0].response;

	const httpClient = HttpClient.createNull({
		[TRANSFORM_ENDPOINT]: [{
			status: 200,
			headers: {
				"content-type": "application/json",
			},
			body: JSON.stringify({
				transformed: response,
			}),
		}],
	});
	return new Rot13Client(httpClient);
}

TypeScript no longer needs the first optional chaining operator (?.):

static createNull(options: NulledRot13ClientResponses = [{
	response: "Nulled Rot13Client response",
}]): Rot13Client {
	const response = options[0]?.response;

	const httpClient = HttpClient.createNull({
		[TRANSFORM_ENDPOINT]: [{
			status: 200,
			headers: {
				"content-type": "application/json",
			},
			body: JSON.stringify({
				transformed: response,
			}),
		}],
	});
	return new Rot13Client(httpClient);
}
6
Before you add support for multiple responses, refactor the code to make it easier.
1. Introduce a variable for the array of HTTP responses.
2. Create a function to make a single configured HTTP response.
3. Have the function be responsible for destructuring the configuration object.
4. Move the default response into the function.
export class Rot13Client {
	// ...

	static createNull(options = [ {} ]) {
		const httpResponses = [ nulledHttpResponse(options[0]) ];

		const httpClient = HttpClient.createNull({
			[TRANSFORM_ENDPOINT]: httpResponses,
		});
		return new Rot13Client(httpClient);
	}

	// ...
}

function nulledHttpResponse({
	response = "Nulled Rot13Client response",
}) {
	return {
		status: 200,
		headers: {
			"content-type": "application/json",
		},
		body: JSON.stringify({
			transformed: response,
		}),
	};
}

TypeScript’s implementation of nulledHttpResponse() needs a default value:

export class Rot13Client {
	// ...

	static createNull(options: NulledRot13ClientResponses = [ {} ]): Rot13Client {
		const httpResponses = [ nulledHttpResponse(options[0]) ];

		const httpClient = HttpClient.createNull({
			[TRANSFORM_ENDPOINT]: httpResponses,
		});
		return new Rot13Client(httpClient);
	}

	// ...
}

function nulledHttpResponse({
	response = "Nulled Rot13Client response",
} = {}) {
	return {
		status: 200,
		headers: {
			"content-type": "application/json",
		},
		body: JSON.stringify({
			transformed: response,
		}),
	};
}
7
You’re ready to add support for multiple responses. Start by updating the test.
Add another response to HttpClient.createNull(), another transformAsync() call (don’t forget to await it), and another assertion.
it("can configure multiple responses", async () => {
	const rot13Client = Rot13Client.createNull([
		{ response: "response 1" },
		{ response: "response 2" },
	]);

	const { response: response1 } = await transformAsync({ rot13Client });
	const { response: response2 } = await transformAsync({ rot13Client });

	assert.equal(response1, "response 1");
	assert.equal(response2, "response 2");
});
8
The test is ready to run.
It should fail, saying that no more HTTP responses are configured.

This is happening because Rot13Client.createNull() is only configuring one response, but the test asking for two.

9
Modify Rot13Client.createNull() configure HttpClient with every response it receives.
You can use options.map((response) => { ... })) with an arrow function to do this.
In your option.map() transformation function, convert the ROT-13 response to an HTTP response.
You can do that with the nulledHttpResponse() function you extracted.
static createNull(options = [ {} ]) {
	const httpResponses = options.map((response) => nulledHttpResponse(response));

	const httpClient = HttpClient.createNull({
		[TRANSFORM_ENDPOINT]: httpResponses,
	});
	return new Rot13Client(httpClient);
}

TypeScript’s nulledHttpResponse() no longer needs a default:

function nulledHttpResponse({
	response = "Nulled Rot13Client response",
}) {
	return {
		status: 200,
		headers: {
			"content-type": "application/json",
		},
		body: JSON.stringify({
			transformed: response,
		}),
	};
}

Complete Solution

Test code:
it("can configure multiple responses", async () => {
	const rot13Client = Rot13Client.createNull([
		{ response: "response 1" },
		{ response: "response 2" },
	]);

	const { response: response1 } = await transformAsync({ rot13Client });
	const { response: response2 } = await transformAsync({ rot13Client });

	assert.equal(response1, "response 1");
	assert.equal(response2, "response 2");
});
Production code (JavaScript):
export class Rot13Client {
	// ...

	static createNull(options = [ {} ]) {
		const httpResponses = options.map((response) => nulledHttpResponse(response));

		const httpClient = HttpClient.createNull({
			[TRANSFORM_ENDPOINT]: httpResponses,
		});
		return new Rot13Client(httpClient);
	}

	// ...
}

function nulledHttpResponse({
	response = "Nulled Rot13Client response",
}) {
	return {
		status: 200,
		headers: {
			"content-type": "application/json",
		},
		body: JSON.stringify({
			transformed: response,
		}),
	};
}
Production code (TypeScript):
export class Rot13Client {
	// ...

	static createNull(options: NulledRot13ClientResponses = [ {} ]): Rot13Client {
		const httpResponses = options.map((response) => nulledHttpResponse(response));

		const httpClient = HttpClient.createNull({
			[TRANSFORM_ENDPOINT]: httpResponses,
		});
		return new Rot13Client(httpClient);
	}

	// ...
}

function nulledHttpResponse({
	response = "Nulled Rot13Client response",
}) {
	return {
		status: 200,
		headers: {
			"content-type": "application/json",
		},
		body: JSON.stringify({
			transformed: response,
		}),
	};
}

Next challenge

Return to module overview