From: Kai Moritz Date: Fri, 10 Jan 2020 19:07:51 +0000 (+0100) Subject: Implemented a narrow integration-test: DemoApplicationIT X-Git-Url: https://juplo.de/gitweb/?p=demos%2Ftesting;a=commitdiff_plain;h=07ca86e8fe8bd302acc1a837f9ee2a62991ab6e7 Implemented a narrow integration-test: DemoApplicationIT * Using testcontainers.org to start a Nginx-server in a container * Using mock-server.org to intercept the HTTP-requests, if needed * Implemented IntegrationTestBase to start Nginx and MockServer * src/test/resources/index.html is needed, because the NginxContainer checks for it * Using Jsoup to pick the remotly fetched text out of the rendered HTML --- diff --git a/pom.xml b/pom.xml index 4c97fe5..67b6dc0 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,8 @@ 1.8 1.12.1 + 5.8.1 + 1.12.4 @@ -51,6 +53,18 @@ ${jsoup.version} test + + org.mock-server + mockserver-netty + ${mockserver.version} + test + + + org.testcontainers + nginx + ${testcontainers-nginx.version} + test + org.springframework.boot diff --git a/src/test/java/de/juplo/demo/DemoApplicationIT.java b/src/test/java/de/juplo/demo/DemoApplicationIT.java new file mode 100644 index 0000000..15d3382 --- /dev/null +++ b/src/test/java/de/juplo/demo/DemoApplicationIT.java @@ -0,0 +1,158 @@ +package de.juplo.demo; + +import static de.juplo.demo.IntegrationTestBase.MOCK_SERVER; +import java.util.regex.Pattern; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.mockserver.matchers.Times.exactly; +import static org.mockserver.model.HttpForward.forward; +import static org.mockserver.model.HttpRequest.request; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.client.WebClient; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +class DemoApplicationIT extends IntegrationTestBase +{ + static Pattern MISSING_PATTERN = + Pattern.compile("^404\\ Not\\ Found\\ from\\ GET\\ http:\\/\\/localhost:[0-9]*/missing.txt$"); + + + @Autowired + WebTestClient webClient; + + + @Test + @DisplayName("HtmlController - Remote-Response: 200") + void testHtmlControllerResponse200() throws Exception + { + MOCK_SERVER + .when(request().withPath("/test.txt"), exactly(1)) + .forward(forward() + .withHost(NGINX.getContainerIpAddress()) + .withPort(NGINX.getMappedPort(80))); + + webClient + .get() + .uri("/?path=test.txt") + .exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class).value(rendered -> + { + Document doc = Jsoup.parse(rendered); + assertThat( + doc.select("html > body > main > div > div > div > pre").text()) + .isEqualTo("Hello World!"); + }); + } + + @Test + @DisplayName("HtmlController - Remote-Response: 404") + void testHtmlControllerResponse404() throws Exception + { + MOCK_SERVER + .when(request().withPath("/missing.txt"), exactly(1)) + .forward(forward() + .withHost(NGINX.getContainerIpAddress()) + .withPort(NGINX.getMappedPort(80))); + + webClient + .get() + .uri("/?path=missing.txt") + .exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class).value(rendered -> + { + Document doc = Jsoup.parse(rendered); + assertThat( + doc.select("html > body > main > div > div > div > pre").text()) + .matches(MISSING_PATTERN); + }); + } + + @Test + @DisplayName("RestController - Remote-Response: 200") + void testRestController200() throws Exception + { + MOCK_SERVER + .when(request().withPath("/test.txt"), exactly(1)) + .forward(forward() + .withHost(NGINX.getContainerIpAddress()) + .withPort(NGINX.getMappedPort(80))); + + webClient + .get() + .uri("/?path=test.txt") + .header("Accept", MediaType.TEXT_PLAIN_VALUE) + .exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class).isEqualTo("Hello World!\n"); + } + + @Test + @DisplayName("RestController - Remote-Response: 404") + void testRestController404() throws Exception + { + MOCK_SERVER + .when(request().withPath("/missing.txt"), exactly(1)) + .forward(forward() + .withHost(NGINX.getContainerIpAddress()) + .withPort(NGINX.getMappedPort(80))); + + webClient + .get() + .uri("/?path=missing.txt") + .header("Accept", MediaType.TEXT_PLAIN_VALUE) + .exchange() + .expectStatus().isNotFound() + .expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON) + .expectBody() + .jsonPath("status").isEqualTo(404) + .jsonPath("error").isEqualTo("Not Found") + .jsonPath("message").isEqualTo( + "Cause: 404 Not Found from GET http://localhost:" + + MOCK_SERVER.getLocalPort() + + "/missing.txt") + .jsonPath("timestamp").exists(); + + } + + @Configuration + static class Application + { + @Bean + RemoteContentService service() + { + return new RemoteContentService( + WebClient + .builder() + .baseUrl("http://localhost:" + MOCK_SERVER.getLocalPort()) + .build()); + } + + @Bean + HtmlController htmlController(RemoteContentService service) + { + return new HtmlController(service); + } + + @Bean + RestController restController(RemoteContentService service) + { + return new RestController(service); + } + } +} diff --git a/src/test/java/de/juplo/demo/IntegrationTestBase.java b/src/test/java/de/juplo/demo/IntegrationTestBase.java new file mode 100644 index 0000000..2135a6d --- /dev/null +++ b/src/test/java/de/juplo/demo/IntegrationTestBase.java @@ -0,0 +1,51 @@ +package de.juplo.demo; + + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockserver.integration.ClientAndServer; +import org.testcontainers.containers.NginxContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; + + + +/** + * This class starts a + * {@link https://www.testcontainers.org/modules/nginx/ NginX-Webserver} + * via a {@link https://www.testcontainers.org/ Testcontainer} and a + * standalone {@link http://www.mock-server.com/#what-is-mockserver MockServer}, + * that can be used to intercept requests for assertions / verifications. + *

+ * We use the {@link + * https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers + * Singleton Pattern} to start the NginX-Testcontainer and start the MockServer + * programmatically via the {@link + * http://www.mock-server.com/mock_server/running_mock_server.html#client_api + * Client-API}. + * @author Kai Moritz + */ +public abstract class IntegrationTestBase +{ + static final NginxContainer NGINX; + static final ClientAndServer MOCK_SERVER; + + + static + { + NGINX = new NginxContainer().withCustomContent("src/test/resources/"); + MOCK_SERVER = ClientAndServer.startClientAndServer(); + } + + + @BeforeAll + static void startMockServer() + { + NGINX.waitingFor(new HttpWaitStrategy()).start(); + } + + @AfterAll + static void stopMockServer() + { + MOCK_SERVER.stop(); + } +} diff --git a/src/test/resources/index.html b/src/test/resources/index.html new file mode 100644 index 0000000..f747925 --- /dev/null +++ b/src/test/resources/index.html @@ -0,0 +1 @@ +DUMMY diff --git a/src/test/resources/test.txt b/src/test/resources/test.txt new file mode 100644 index 0000000..980a0d5 --- /dev/null +++ b/src/test/resources/test.txt @@ -0,0 +1 @@ +Hello World!