Challenge #3: Configuring Responses

In the previous three challenges, you made the code call the ROT-13 server when it received a POST request. In this challenge, you’ll make it return a web page containing the encoded text.

Instructions

1. Implement the "POST renders result of ROT-13 service call" test:

  1. Configure the ROT-13 client to return "my_response".
  2. Assert that HomePageController.postAsync() returns homePageView.homePage("my_response").

2. Revise HomePageController.postAsync():

  1. Render and return the home page.
  2. Don’t worry about edge cases—just program the happy path.

3. Verify that it worked:

  1. Run ./serve_dev.sh 5010 5011 from a command prompt, if it’s not already running.
  2. Visit http://localhost:5010 in a browser.
  3. Enter "hello" into the text field and press the button.
  4. If it worked, the contents of the text field will change to "uryyb".

Remember to commit your changes when you’re done.

API Documentation

const rot13Client = Rot13Client.createNull([{ response }]);

Create a Nulled Rot13Client that responds with the specified value the first time it’s called. Note that the parameter is an array of objects. To specify additional responses, add more { response } objects to the array.

  • response (string) - the response to return
  • returns rot13Client (Rot13Client) - the ROT-13 client
const response = homePageView.homePage(text)

Render the home page with the specified string in the text field.

  • text (string) - the string to put in the text field
  • returns response (HttpResponse) - the home page

JavaScript Primers

No new concepts.

Hints

1
Just like in the previous challenges, you’ll need to create a Rot13Client in your test. But this time, you’ll need to control what data it returns.
You can do that by providing an array of objects to Rot13Client.createNull().
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);

	// Act

	// Assert
});
2
You’ll need a HomePageController and a Clock, too, but you don’t need to track requests.
This is the same as previous challenges.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	// Act

	// Assert
});
3
You’ll need an HttpServerRequest with a valid request body, or your parsing logic will fail. Use a body that makes it clear it’s not part of the test.
You can specify it the same way as in the last challenge.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});

	// Act

	// Assert
});
4
You’ll need a WwwConfig, but you don’t need to configure a specific port.
This is just like the first challenge.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});
	const config = WwwConfig.createTestInstance();

	// Act

	// Assert
});
5
You’re ready to simulate the POST request.
This uses controller.postAsync(), just like the previous challenge, except that you need to keep the response.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});
	const config = WwwConfig.createTestInstance();

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

	// Assert
});
6
Before you write your assertion, you’ll need an expected value.
You’re expecting the home page with "my_response" in the text field.
You can use homePageView.homePage() for that.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});
	const config = WwwConfig.createTestInstance();

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

	// Assert
	const expected = homePageView.homePage("my_response");
});
7
Now you can assert that the response matches the assertion.
This is the same as the first challenge.
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});
	const config = WwwConfig.createTestInstance();

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

	// Assert
	const expected = homePageView.homePage("my_response");
	assert.deepEqual(response, expected);
});
8
You’re ready to run the test.
It should fail, saying that the response was undefined.

This means your production code isn’t returning a response.

9
Your production code needs to return a response.
You can do that by calling homePageView.homePage(output).
The output comes from the ROT-13 service.
Specifically, the call to rot13Client.transformAsync().
async postAsync(request, config) {
	const form = await request.readBodyAsUrlEncodedFormAsync();
	const userInput = form[INPUT_FIELD_NAME][0];

	const output = await this._rot13Client.transformAsync(
		config.rot13ServicePort,
		userInput,
		config.correlationId,
	);

	return homePageView.homePage(output);
}

TypeScript no longer needs a placeholder return, but userInput still needs a temporary workaround:

// TypeScript
async postAsync(request: HttpServerRequest, config: WwwConfig): Promise<HttpServerResponse> {
	const form = await request.readBodyAsUrlEncodedFormAsync();
	const userInput = form[INPUT_FIELD_NAME]?.[0] ?? "not implemented";

	const output = await this._rot13Client.transformAsync(
		config.rot13ServicePort,
		userInput,
		config.correlationId,
	);

	return homePageView.homePage(output);
}

Complete Solution

Test code:
it("POST renders result of ROT-13 service call", async () => {
	// Arrange
	const rot13Client = Rot13Client.createNull([{
		response: "my_response",
	}]);
	const clock = Clock.createNull();
	const controller = new HomePageController(rot13Client, clock);

	const request = HttpServerRequest.createNull({
		body: "text=irrelevant_text",
	});
	const config = WwwConfig.createTestInstance();

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

	// Assert
	const expected = homePageView.homePage("my_response");
	assert.deepEqual(response, expected);
});
Production code (JavaScript):
async postAsync(request, config) {
	const form = await request.readBodyAsUrlEncodedFormAsync();
	const userInput = form[INPUT_FIELD_NAME][0];

	const output = await this._rot13Client.transformAsync(
		config.rot13ServicePort,
		userInput,
		config.correlationId,
	);
	return homePageView.homePage(output);
}
Production code (TypeScript):
async postAsync(request: HttpServerRequest, config: WwwConfig): Promise<HttpServerResponse> {
	const form = await request.readBodyAsUrlEncodedFormAsync();
	const userInput = form[INPUT_FIELD_NAME]?.[0] ?? "not implemented";

	const output = await this._rot13Client.transformAsync(
		config.rot13ServicePort,
		userInput,
		config.correlationId,
	);

	return homePageView.homePage(output);
}

Next challenge

Return to module overview