Implemented a narrow integration-test: DemoApplicationIT
authorKai Moritz <kai@juplo.de>
Fri, 10 Jan 2020 19:07:51 +0000 (20:07 +0100)
committerKai Moritz <kai@juplo.de>
Fri, 28 Aug 2020 09:09:11 +0000 (11:09 +0200)
* 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

pom.xml
src/test/java/de/juplo/demo/DemoApplicationIT.java [new file with mode: 0644]
src/test/java/de/juplo/demo/IntegrationTestBase.java [new file with mode: 0644]
src/test/resources/index.html [new file with mode: 0644]
src/test/resources/test.txt [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 4c97fe5..67b6dc0 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,8 @@
        <properties>
                <java.version>1.8</java.version>
                <jsoup.version>1.12.1</jsoup.version>
+               <mockserver.version>5.8.1</mockserver.version>
+               <testcontainers-nginx.version>1.12.4</testcontainers-nginx.version>
        </properties>
 
        <dependencies>
                        <version>${jsoup.version}</version>
                        <scope>test</scope>
                </dependency>
+               <dependency>
+                       <groupId>org.mock-server</groupId>
+                       <artifactId>mockserver-netty</artifactId>
+                       <version>${mockserver.version}</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.testcontainers</groupId>
+                       <artifactId>nginx</artifactId>
+                       <version>${testcontainers-nginx.version}</version>
+                       <scope>test</scope>
+               </dependency>
 
                <dependency>
                        <groupId>org.springframework.boot</groupId>
diff --git a/src/test/java/de/juplo/demo/DemoApplicationIT.java b/src/test/java/de/juplo/demo/DemoApplicationIT.java
new file mode 100644 (file)
index 0000000..15d3382
--- /dev/null
@@ -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 (file)
index 0000000..2135a6d
--- /dev/null
@@ -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.
+ * <p>
+ * 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 (file)
index 0000000..f747925
--- /dev/null
@@ -0,0 +1 @@
+DUMMY
diff --git a/src/test/resources/test.txt b/src/test/resources/test.txt
new file mode 100644 (file)
index 0000000..980a0d5
--- /dev/null
@@ -0,0 +1 @@
+Hello World!