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
-
For the first test,
"GET renders home page"
, extract a helper method with this signature:const { response } = await getAsync();
-
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 });
For the third test,
"POST renders result of ROT-13 service call"
, modify your newpostAsync()
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.