{"id":1229,"date":"2022-01-23T09:42:53","date_gmt":"2022-01-23T16:42:53","guid":{"rendered":"https:\/\/bullyrooks.com\/?p=1229"},"modified":"2022-01-24T09:46:15","modified_gmt":"2022-01-24T16:46:15","slug":"kube-cloud-pt2-automated-testing-with-testcontainers","status":"publish","type":"post","link":"https:\/\/bullyrooks.com\/index.php\/2022\/01\/23\/kube-cloud-pt2-automated-testing-with-testcontainers\/","title":{"rendered":"Kube Cloud Pt2 | Automated Testing with TestContainers"},"content":{"rendered":"\n<p>In the last section we implemented service endpoint that stored data in a mongodb backend.  In this session we&#8217;re going to build a component test to automatically verify that functionality.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">TestContainer Overview<\/h2>\n\n\n\n<p>TestContainers allow us to mock external resources with a docker based implementation.  This is similar to &#8220;regular&#8221; mocking (with, for example, embedded databases) in that we can run our tests within our build environment and get immediate feedback.  However, TestContainers use actual implementations of resources running within docker containers which offers higher fidelity to the actual implementation that we&#8217;ll be deploying against.  This does come at some cost though, since these are docker images, we&#8217;ll have a dependency on an external resource (the docker registry and the images it hosts) within our build.  While not normally a problem, keep in mind that network issues, rate limiting of docker hosted registries, etc&#8230; could cause your build.  We normally want to avoid externalities in our build processes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"256\" height=\"342\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/01\/image-47.png?resize=256%2C342&#038;ssl=1\" alt=\"\" class=\"wp-image-1250\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/01\/image-47.png?w=256&amp;ssl=1 256w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/01\/image-47.png?resize=225%2C300&amp;ssl=1 225w\" sizes=\"auto, (max-width: 256px) 100vw, 256px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Implementing the TestContainer<\/h2>\n\n\n\n<p>Add these dependencies to your pom.xml<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\n    &lt;properties&gt;\n...\n        &lt;javafaker.version&gt;0.15&lt;\/javafaker.version&gt;\n...\n    &lt;\/properties&gt;\n...\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.testcontainers&lt;\/groupId&gt;\n            &lt;artifactId&gt;mongodb&lt;\/artifactId&gt;\n            &lt;version&gt;1.16.2&lt;\/version&gt;\n            &lt;scope&gt;test&lt;\/scope&gt;\n        &lt;\/dependency&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;com.github.javafaker&lt;\/groupId&gt;\n            &lt;artifactId&gt;javafaker&lt;\/artifactId&gt;\n            &lt;version&gt;${javafaker.version}&lt;\/version&gt;\n        &lt;\/dependency&gt;<\/code><\/pre>\n\n\n\n<p>First, I&#8217;m going to modify a few thing.  First, add a builder to the <code>CreateMessageRequestDTO <\/code>object so I can build test data easier:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.bullyrooks.cloud_application.controller.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class CreateMessageRequestDTO {\n    private String firstName;\n    private String lastName;\n    private String message;\n}\n<\/code><\/pre>\n\n\n\n<p>Then I&#8217;m going to update <code>CreateMessageResponseDTO <\/code>so that I can return the <code>messageId<\/code>, so I can run my verifications<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.bullyrooks.cloud_application.controller.dto;\n\nimport lombok.Data;\n\n@Data\npublic class CreateMessageResponseDTO {\n    private String messageId;\n    private String firstName;\n    private String lastName;\n    private String message;\n}\n<\/code><\/pre>\n\n\n\n<p>Create a new class called <code>MessageControllerTest<\/code> in the test path under the <code>com.bullyrooks.cloud_application.controller<\/code> package.  Add this code<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">package com.bullyrooks.cloud_application.controller;<br><br>import com.bullyrooks.cloud_application.controller.dto.CreateMessageRequestDTO;<br>import com.bullyrooks.cloud_application.controller.dto.CreateMessageResponseDTO;<br>import com.bullyrooks.cloud_application.repository.MessageRepository;<br>import com.bullyrooks.cloud_application.repository.document.MessageDocument;<br>import com.github.javafaker.Faker;<br>import com.github.javafaker.service.FakeValuesService;<br>import com.github.javafaker.service.RandomService;<br>import lombok.extern.slf4j.Slf4j;<br>import org.junit.jupiter.api.Test;<br>import org.junit.jupiter.api.extension.ExtendWith;<br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;<br>import org.springframework.boot.test.context.SpringBootTest;<br>import org.springframework.boot.web.server.LocalServerPort;<br>import org.springframework.core.ParameterizedTypeReference;<br>import org.springframework.http.HttpEntity;<br>import org.springframework.http.HttpHeaders;<br>import org.springframework.http.HttpMethod;<br>import org.springframework.http.MediaType;<br>import org.springframework.http.ResponseEntity;<br>import org.springframework.test.context.ActiveProfiles;<br>import org.springframework.test.context.DynamicPropertyRegistry;<br>import org.springframework.test.context.DynamicPropertySource;<br>import org.springframework.test.context.junit.jupiter.SpringExtension;<br>import org.springframework.web.client.RestTemplate;<br>import org.springframework.web.util.UriComponents;<br>import org.springframework.web.util.UriComponentsBuilder;<br>import org.testcontainers.containers.MongoDBContainer;<br><br>import java.util.List;<br>import java.util.Locale;<br><br>import static org.junit.jupiter.api.Assertions.<em>assertEquals<\/em>;<br>import static org.mockito.Mockito.<em>when<\/em>;<br><br>@ExtendWith(SpringExtension.class)<br>@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.<em>RANDOM_PORT<\/em>)<br>@Slf4j<br>@AutoConfigureMockMvc<br>@ActiveProfiles(\"test\")<br>public class MessageControllerTest {<br><br>    static MongoDBContainer <em>mongoDBContainer <\/em>= new      MongoDBContainer(\"mongo:4.4.10\");<br>    {<br>        <em>mongoDBContainer<\/em>.start();<br>    }<br>    @DynamicPropertySource<br>    static void setProperties(DynamicPropertyRegistry registry) {<br>        registry.add(\"spring.data.mongodb.uri\", <em>mongoDBContainer<\/em>::getReplicaSetUrl);<br>    }<br>    private final String MESSAGE_PATH = \"\/message\";<br><br>    @LocalServerPort<br>    int randomServerPort;<br><br>    @Autowired<br>    MessageRepository messageRepository;<br><br>    FakeValuesService fakesvc = new FakeValuesService(<br>            new Locale(\"en-US\"), new RandomService());<br>    Faker faker = new Faker();<br><br><br>    @Test<br>    void testGetOpportunitiesForNotification(){<br>        Long userId = 1l;<br><br>        \/\/given<br>        CreateMessageRequestDTO request = CreateMessageRequestDTO<br>                .<em>builder<\/em>()<br>                .firstName(faker.name().firstName())<br>                .lastName(faker.name().lastName())<br>                .message(faker.gameOfThrones().quote())<br>                .build();<br><br>        \/\/when<br>        RestTemplate restTemplate = new RestTemplate();<br>        String baseUrl = \"http:\/\/localhost:\" + randomServerPort + MESSAGE_PATH;<br>        UriComponents builder = UriComponentsBuilder.<em>fromHttpUrl<\/em>(baseUrl)<br>                .build();<br><br>        ResponseEntity&lt;CreateMessageResponseDTO&gt; result = restTemplate.postForEntity(<br>                builder.toUri(), request, CreateMessageResponseDTO.class);<br><br>        \/\/then<br>        CreateMessageResponseDTO dto = result.getBody();<br>        <em>assertEquals<\/em>(request.getFirstName(), dto.getFirstName());<br>        <em>assertEquals<\/em>(request.getLastName(), dto.getLastName());<br>        <em>assertEquals<\/em>(request.getMessage(), dto.getMessage());<br><br>        MessageDocument savedDoc = messageRepository.findById(dto.getMessageId()).get();<br>        <em>assertEquals<\/em>(request.getFirstName(), savedDoc.getFirstName());<br>        <em>assertEquals<\/em>(request.getLastName(), savedDoc.getLastName());<br>        <em>assertEquals<\/em>(request.getMessage(), savedDoc.getMessage());<br>    }<br><br>}<\/pre>\n\n\n\n<p>Let me explain what&#8217;s going on here.  First, I&#8217;m telling maven (and spring) that I have a dependency on a docker image that will allow me to mock MongoDB.  I&#8217;m also telling spring boot test to stand that image up in this test and wherever I reference my mongodb uri, use the docker instance (via  <code>spring.data.mongodb.<\/code>uri )<\/p>\n\n\n\n<p>Next, I&#8217;m standing up an instance of faker, which will allow me to mock up test data in a random way, so that test data should never collide.<\/p>\n\n\n\n<p>Then I&#8217;m making a request against my controller endpoint, getting the response and verifying the response matches the request.<\/p>\n\n\n\n<p>Finally, I&#8217;m going to the database instance (the docker image) and verifying the state of the database for my <code>messageId<\/code>.  This confirms that the record was &#8216;written&#8217;.<\/p>\n\n\n\n<p>Run this test and it should pass.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Git Commit and Push<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>$ git add .\nwarning: LF will be replaced by CRLF in pom.xml.\nThe file will have its original line endings in your working directory\n\n$ git commit -m \"new component test\"\n&#91;database 813361e] new component test\n 4 files changed, 119 insertions(+)\n create mode 100644 src\/test\/java\/com\/bullyrooks\/cloud_application\/controller\/MessageControllerTest.java\n\n$ git push\nEnumerating objects: 35, done.\nCounting objects: 100% (35\/35), done.\nDelta compression using up to 4 threads\nCompressing objects: 100% (13\/13), done.\nWriting objects: 100% (20\/20), 2.76 KiB | 706.00 KiB\/s, done.\nTotal 20 (delta 5), reused 0 (delta 0), pack-reused 0\nremote: Resolving deltas: 100% (5\/5), completed with 5 local objects.\nTo github.com-bullyrook:bullyrooks\/cloud_application.git\n   f6b7f59..813361e  database -&gt; database\n<\/code><\/pre>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"entry-summary\">\nIn the last section we implemented service endpoint that stored data in&hellip;\n<\/div>\n<div class=\"link-more\"><a href=\"https:\/\/bullyrooks.com\/index.php\/2022\/01\/23\/kube-cloud-pt2-automated-testing-with-testcontainers\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &ldquo;Kube Cloud Pt2 | Automated Testing with TestContainers&rdquo;<\/span>&hellip;<\/a><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41],"tags":[50,42,43,158],"course":[154],"class_list":["post-1229","post","type-post","status-publish","format-standard","hentry","category-software-development","tag-maven","tag-spring","tag-spring-boot","tag-testcontainers","course-kubernetes-application-hosted-in-the-cloud-pt-2","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":820,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-17-3867eb38d8f1\/","url_meta":{"origin":1229,"position":0},"title":"Containerizing our UI","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"We\u2019ve got a simple page that calls one of our endpoints working. Let\u2019s go ahead an containerize it so that we can deploy it into our k8s environment in future steps. Build the Dockerfile I\u2019m following the process laid out here, with some minor differences. Create a Dockerfile # Stage\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":627,"url":"https:\/\/bullyrooks.com\/index.php\/2021\/01\/25\/why-use-docker-on-synology\/","url_meta":{"origin":1229,"position":1},"title":"Why Use Docker on Synology?","author":"Bullyrook","date":"January 25, 2021","format":false,"excerpt":"There's lots of reasons why docker is a good fit for your synology server. Among them are Better maintained applications. Some synology packages are not maintained very well.Better supported applications. Documentation for docker images is usually very good.Isolation from the underlying OS. You don't need to worry about dependency versioning\u2026","rel":"","context":"In &quot;Home Networking&quot;","block_context":{"text":"Home Networking","link":"https:\/\/bullyrooks.com\/index.php\/category\/technology\/home-networking\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2021\/01\/image-31.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1001,"url":"https:\/\/bullyrooks.com\/index.php\/2021\/02\/21\/setup-and-install-nextcloud-on-synology\/","url_meta":{"origin":1229,"position":2},"title":"Setup and Install NextCloud On Synology","author":"Bullyrook","date":"February 21, 2021","format":false,"excerpt":"NextCloud is a file storage and sharing system. It can be compared to dropbox or google drive. The difference here is that you're using your NAS, so you'll get as much space as you can support without paying for additional storage. NextCloud also offers multi-platform compatibility via a web application\u2026","rel":"","context":"In &quot;Home Networking&quot;","block_context":{"text":"Home Networking","link":"https:\/\/bullyrooks.com\/index.php\/category\/technology\/home-networking\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":826,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-9-55fd26c1dffb\/","url_meta":{"origin":1229,"position":3},"title":"Docker Registry","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"In the previous article we created a docker image as part of our build process and stored it into our local repository. In order to deploy it in an automated fashion we should create a remote image repository in the cloud. This will expand the types of tools that we\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":703,"url":"https:\/\/bullyrooks.com\/index.php\/2021\/01\/28\/installing-jackett\/","url_meta":{"origin":1229,"position":4},"title":"Installing Jackett","author":"Bullyrook","date":"January 28, 2021","format":false,"excerpt":"Jackett is a database for bittorrent indexers. This allows you to refer to internal resources when configuring radarr and sonarr, which then get mapped to external API. It makes it really easy to add more bittorrent indexers to expand the breadth of your searches. Running Jackett Container The docker project\u2026","rel":"","context":"In &quot;Home Networking&quot;","block_context":{"text":"Home Networking","link":"https:\/\/bullyrooks.com\/index.php\/category\/technology\/home-networking\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2021\/01\/image-79.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":837,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-8-3e94686c9a34\/","url_meta":{"origin":1229,"position":5},"title":"Containerize the Service With Docker","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"Now we\u2019re going to take our functional service and containerize it. This will allow us to control the deployment environment that our java spring boot service runs in as well as provide some additional security and isolation from application updates that can break our application. Containerization Overview If you\u2019ve spent\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1229","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/comments?post=1229"}],"version-history":[{"count":3,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1229\/revisions"}],"predecessor-version":[{"id":1251,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1229\/revisions\/1251"}],"wp:attachment":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/media?parent=1229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/categories?post=1229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/tags?post=1229"},{"taxonomy":"course","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/course?post=1229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}