Challenge #4: Signature Shielding

The tests are fast and reliable, but they’re a bit... verbose. In this challenge, you’ll refactor to make them easier to read and modify. This introduces the Signature Shielding pattern.

Instructions

  1. For the first test, "GET renders home page", extract a helper method with this signature:

    const { response } = await getAsync();
  2. For the second test, "POST asks ROT-13 service to transform text", extract a helper method with this signature:

    const { rot13Requests } = await postAsync({ body, rot13ServicePort, correlationId });
  3. For the third test, "POST renders result of ROT-13 service call", modify your new postAsync() method to support this signature:

    const { response } = await postAsync({ rot13Response });

Remember to commit your changes when you’re done.

Useful Constants

const IRRELEVANT_PORT = 42;

This constant is available in the test code. Use it when the ROT-13 service’s port doesn’t matter.

const IRRELEVANT_INPUT = "irrelevant_input";

This constant is available in the test code. Use it when the user’s input doesn’t matter.

const IRRELEVANT_CORRELATION_ID = "irrelevant-correlation-id";

This constant is available in the test code. Use it when the correlation ID doesn’t matter.

JavaScript Primers

Hints

"GET renders home page" test:

1
Refactor the “Arrange” and “Act” parts of the "GET renders home page" test into a separate function named getAsync(). Don’t forget the async and await keywords.
Your editor might have an automatic “Extract Method” or “Extract Function” refactoring that does this for you.
it("GET renders home page", async () => {
	const response = await getAsync();

	// Assert
	const expected = homePageView.homePage();
	assert.deepEqual(response, expected);
});

async function getAsync() {
	// Arrange
	const rot13Client = Rot13Client.createNull();
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull();
	const config = WwwConfig.createTestInstance();

	// Act
	const response = await controller.getAsync(request, config);
	return response;
}
2
Modify getAsync()’s return value to match the desired signature.
You’ll need object destructuring in the test and object shorthand in getAsync().
it("GET renders home page", async () => {
	const { response } = await getAsync();

	// Assert
	const expected = homePageView.homePage();
	assert.deepEqual(response, expected);
});

async function getAsync() {
	// Arrange
	const rot13Client = Rot13Client.createNull();
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull();
	const config = WwwConfig.createTestInstance();

	// Act
	const response = await controller.getAsync(request, config);
	return { response };
}
3
Finish up the refactoring by removing unnecessary variables, comments, and whitespace.
Your editor might have an automatic “Inline Variable” refactoring.
it("GET renders home page", async () => {
	const { response } = await getAsync();
	assert.deepEqual(response, homePageView.homePage());
});

async function getAsync() {
	const rot13Client = Rot13Client.createNull();
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull();
	const config = WwwConfig.createTestInstance();

	const response = await controller.getAsync(request, config);
	return { response };
}

"POST asks ROT-13 service to transform text" test:

4
Make the “Arrange” and “Act” parts of the "POST asks ROT-13 service to transform text" test look like a generic function.
Extract the test-specific constants into variables and move them to the top of the function.
Your editor might have an automatic “Introduce Variable” refactoring.
it("POST asks ROT-13 service to transform text", async () => {
	const body = "text=hello%20world";
	const rot13ServicePort = 999;
	const correlationId = "my-correlation-id";

	// Arrange
	const rot13Client = Rot13Client.createNull();
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: body,
	});
	const config = WwwConfig.createTestInstance({
		rot13ServicePort: rot13ServicePort,
		correlationId: correlationId,
	});

	 // Act
	await controller.postAsync(request, config);

	 // Assert
	assert.deepEqual(rot13Requests.data, [{
		port: 999,
		text: "hello world",
		correlationId: "my-correlation-id",
	}]);
});
5
Refactor the newly-generic “Arrange” and “Act” parts of the test into a function named postAsync. Don’t forget the async and await keywords.
it("POST asks ROT-13 service to transform text", async () => {
	const body = "text=hello%20world";
	const rot13ServicePort = 999;
	const correlationId = "my-correlation-id";
	const rot13Requests = await postAsync(body, rot13ServicePort, correlationId);

	// Assert
	assert.deepEqual(rot13Requests.data, [{
		port: 999,
		text: "hello world",
		correlationId: "my-correlation-id",
	}]);
});

async function postAsync(body, rot13ServicePort, correlationId) {
	// Arrange
	const rot13Client = Rot13Client.createNull();
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: body,
	});
	const config = WwwConfig.createTestInstance({
		rot13ServicePort: rot13ServicePort,
		correlationId: correlationId,
	});

	// Act
	await controller.postAsync(request, config);
	return rot13Requests;
}
6
Modify postAsync()’s parameters and return value to match the desired signature.
You’ll need to use object shorthand and object destructuring.
it("POST asks ROT-13 service to transform text", async () => {
	const body = "text=hello%20world";
	const rot13ServicePort = 999;
	const correlationId = "my-correlation-id";
	const { rot13Requests } = await postAsync({ body, rot13ServicePort, correlationId });

	// Assert
	assert.deepEqual(rot13Requests.data, [{
		port: 999,
		text: "hello world",
		correlationId: "my-correlation-id",
	}]);
});

async function postAsync({ body, rot13ServicePort, correlationId }) {
	// Arrange
	const rot13Client = Rot13Client.createNull();
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: body,
	});
	const config = WwwConfig.createTestInstance({
		rot13ServicePort: rot13ServicePort,
		correlationId: correlationId,
	});

	// Act
	await controller.postAsync(request, config);
	return { rot13Requests };
}
7
Finish up the refactoring by removing unnecessary variables, comments, and whitespace.
Your editor might have an automatic “Inline Variable” refactoring.
it("POST asks ROT-13 service to transform text", async () => {
	const { rot13Requests } = await postAsync({
		body: "text=hello%20world",
		rot13ServicePort: 999,
		correlationId: "my-correlation-id",
	});

	assert.deepEqual(rot13Requests.data, [{
		text: "hello world",
		port: 999,
		correlationId: "my-correlation-id",
	}]);
});

async function postAsync({ body, rot13ServicePort, correlationId }) {
	const rot13Client = Rot13Client.createNull();
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({ body });
	const config = WwwConfig.createTestInstance({ rot13ServicePort, correlationId });

	await controller.postAsync(request, config);
	return { rot13Requests };
}

"POST renders result of ROT-13 service call" test:

8
To support the rot13Request parameter, you’ll need to make the current parameters optional by providing default function parameters.
Remember to make the overall object optional, too. None of your tests need it yet, but it doesn’t cost anything and could save some troubleshooting in the future.
Use the IRRELEVANT_XXX constants.
For the body, remember to include the name of the text field. You can use string interpolation for that.
async function postAsync({
	body = `text=${IRRELEVANT_INPUT}`,
	rot13ServicePort = IRRELEVANT_PORT,
	correlationId = IRRELEVANT_CORRELATION_ID,
} = {}) {
	const rot13Client = Rot13Client.createNull();
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({ body });
	const config = WwwConfig.createTestInstance({ rot13ServicePort, correlationId });

	await controller.postAsync(request, config);
	return { rot13Requests };
}
9
Make postAsync() support the rot13Response parameter.
Add it to the method signature with a default value.
Use it in Rot13Client.createNull().
async function postAsync({
	body = `text=${IRRELEVANT_INPUT}`,
	rot13ServicePort = IRRELEVANT_PORT,
	correlationId = IRRELEVANT_CORRELATION_ID,
	rot13Response = "irrelevant ROT-13 response",
} = {}) {
	const rot13Client = Rot13Client.createNull([{ response: rot13Response }]);
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({ body });
	const config = WwwConfig.createTestInstance({ rot13ServicePort, correlationId });

	await controller.postAsync(request, config);
	return { rot13Requests };
}
10
Return the controller’s response from postAsync().
Put the controller’s response in a variable.
Use object shorthand to return it from the function.
async function postAsync({
	body = `text=${IRRELEVANT_INPUT}`,
	rot13ServicePort = IRRELEVANT_PORT,
	correlationId = IRRELEVANT_CORRELATION_ID,
	rot13Response = "irrelevant ROT-13 response",
} = {}) {
	const rot13Client = Rot13Client.createNull([{ response: rot13Response }]);
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({ body });
	const config = WwwConfig.createTestInstance({ rot13ServicePort, correlationId });

	const response = await controller.postAsync(request, config);
	return { response, rot13Requests };
}
11
Modify the "POST renders result of ROT-13 service call" test to use postAsync().
Factor the ROT-13 client response into a variable and move it to the top of the function.
Replace the newly-generic “Arrange” and “Act” code with a call to postAsync(). (Don’t forget to await it.)
Inline the ROT-13 client response and clean up.
it("POST renders result of ROT-13 service call", async () => {
	const { response } = await postAsync({ rot13Response: "my_response" });
	assert.deepEqual(response, homePageView.homePage("my_response"));
});

Complete Solution

Test code:
describe.only("Home Page Controller", () => {

	describe("happy paths", () => {

		it("GET renders home page", async () => {
			const { response } = await getAsync();
			assert.deepEqual(response, homePageView.homePage());
		});

		it("POST asks ROT-13 service to transform text", async () => {
			const { rot13Requests } = await postAsync({
				body: "text=hello%20world",
				rot13ServicePort: 999,
				correlationId: "my-correlation-id",
			});

			assert.deepEqual(rot13Requests.data, [{
				text: "hello world",
				port: 999,
				correlationId: "my-correlation-id",
			}]);
		});

		it("POST renders result of ROT-13 service call", async () => {
			const { response } = await postAsync({ rot13Response: "my_response" });
			assert.deepEqual(response, homePageView.homePage("my_response"));
		});

	});

	// ...

});


async function getAsync() {
	const rot13Client = Rot13Client.createNull();
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull();
	const config = WwwConfig.createTestInstance();

	const response = await controller.getAsync(request, config);
	return { response };
}

async function postAsync({
	body = `text=${IRRELEVANT_INPUT}`,
	rot13ServicePort = IRRELEVANT_PORT,
	correlationId = IRRELEVANT_CORRELATION_ID,
	rot13Response = "irrelevant ROT-13 response",
} = {}) {
	const rot13Client = Rot13Client.createNull([{ response: rot13Response }]);
	const rot13Requests = rot13Client.trackRequests();

	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({ body });
	const config = WwwConfig.createTestInstance({ rot13ServicePort, correlationId });

	const response = await controller.postAsync(request, config);
	return { response, rot13Requests };
}

Production code is unchanged.

Next challenge

Return to module overview