<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JUnit 5 Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/junit-5/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:36 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>JUnit 5 Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Integration Tests for WebClient with WireMock</title>
		<link>https://blog.codersee.com/integration-tests-webclient-wiremock/</link>
					<comments>https://blog.codersee.com/integration-tests-webclient-wiremock/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 28 Feb 2024 10:56:57 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[JUnit 5]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[WebClient]]></category>
		<category><![CDATA[WireMock]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508747</guid>

					<description><![CDATA[<p>Learn how to perform integration testing for Spring WebClient that invokes external REST APIs with WireMock and JUnit 5.</p>
<p>The post <a href="https://blog.codersee.com/integration-tests-webclient-wiremock/">Integration Tests for WebClient with WireMock</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hi guys! 🙂 Today we will learn how to do <strong>integration testing</strong> for <strong>Spring WebClient</strong> that invokes external REST APIs with <strong>WireMock</strong> and <strong>JUnit 5</strong>. </p>



<p>As an example, we will implement a simple logic responsible for querying the <strong>GitHub API</strong> using coroutines. Nevertheless, I am pretty sure you will be able to easily adjust this article to your needs. (of course, if you are here only for the WireMock part, then I recommend skipping to the &#8220;WebClient Integration Testing With WireMock&#8221; chapter).</p>



<p>Lastly, I just wanted to add that you can use this testing approach regardless of whether you are using WebClient, Retrofit, Ktor Client, or any other HTTP client. </p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/integration-tests-webclient-wiremock/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2Fhbr4snySA6I%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-which-webclient-testing-strategy-is-the-best">Which WebClient Testing Strategy Is The Best?</h2>



<p>But before we head to the practice part, let&#8217;s try to answer the above question. </p>



<p>Quick answer- there is no such thing as <strong>the best testing strategy</strong>. </p>



<p>And for the longer answer, let&#8217;s take a look at the visualization of the testing pyramid first:</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="1024" src="http://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-1024x1024.png" alt="Image presents a diagram with testing pyramid including e2e tests, integration tests, and unit tests helping to better understand the best approach for WebClient WireMock testing. " class="wp-image-9508749" style="width:542px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-1024x1024.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-300x300.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-150x150.png 150w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-768x768.png 768w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid.png 1080w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The tests are ordered based on their scope and execution time. The closer we are to the top, the longer the tests take, but they also give us more confidence. </p>



<ul class="wp-block-list">
<li><strong>unit tests</strong> form the base, as they are quick to run and pinpoint specific code issues,</li>



<li><strong>integration tests</strong> come next, validating the interactions between units,</li>



<li><strong>E2E tests</strong>, with a broader scope, are placed at the top.</li>
</ul>



<p>So long story short, although the e2e tests give us the most confidence, we cannot rely only on them, because the feedback loop would take way too long. And this would affect the development process.</p>



<p>So usually, we make concessions and introduce more integration and unit tests. </p>



<p>But which of these two should we pick for the WebClient? </p>



<p>In my opinion- <strong>integration tests</strong>. </p>



<p>Whenever we are talking about logic responsible for communicating with external API, we must make sure that: </p>



<ul class="wp-block-list">
<li>we query the appropriate URL, </li>



<li>we pass the necessary headers with appropriate values, </li>



<li>request/response bodies are properly serialized/deserialized, </li>



<li>and many, many more. </li>
</ul>



<p>With unit testing, the amount of mocking here would be so significant that I would question whether we need such a test at all. </p>



<p>Moreover, in Spring we can write integration tests for WebClient with WireMock at almost no effort. </p>



<h2 class="wp-block-heading" id="h-set-up-github-api-key">Set Up GitHub API Key </h2>



<p>With that said, let&#8217;s set up the API key we will use to query the GitHub API. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Note:</strong> technically, the endpoint we will use does not require <strong>any token.</strong> However, for the learning purposes I want to show you how to deal with passing bearer tokens, too 🙂 </cite></blockquote>



<p>As the first step, let&#8217;s navigate to the <a href="https://github.com/settings/tokens" target="_blank" rel="noreferrer noopener">tokens tab</a> in GitHub settings and click the &#8220;Generate new token (classic)&#8221;: </p>



<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="777" height="190" src="http://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1.png" alt="Image is a screenshot and presents how to generate new classic GitHub token." class="wp-image-9508752" style="width:777px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1.png 777w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1-300x73.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1-768x188.png 768w" sizes="(max-width: 777px) 100vw, 777px" /></figure>



<p>On the next page, let&#8217;s specify the name, expiration, and desired scopes: </p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="780" height="457" src="http://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2.png" alt="Image is a screenshot from GitHub tokens page." class="wp-image-9508753" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2.png 780w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2-300x176.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2-768x450.png 768w" sizes="(max-width: 780px) 100vw, 780px" /></figure>



<p></p>



<p>If you don&#8217;t know what scopes you will need, please follow the principle of lowest privilege. At the end of the day, we can generate a new token at some point in the future when the requirements change.</p>



<p>When we are ready, let&#8217;s hit the generate button <strong>copy the token value</strong>, and <strong>store it securely</strong>. We will not see it again! </p>



<h2 class="wp-block-heading" id="h-query-the-api-with-webclient">Query The Api With WebClient</h2>



<p>Before we implement the WireMock tests, let&#8217;s prepare a simple project responsible for querying the external API with WebClient. So, even if you are here only for the WireMock part, I encourage you to briefly check this paragraph to be on the same page 😉 </p>



<p>Long story short, we will implement WebClient with coroutines that will query the &#8220;List repositories for a user&#8221; endpoint. For more details about it, check out the <a href="https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user" target="_blank" rel="noreferrer noopener">GitHub API documentation</a>, please. </p>



<h3 class="wp-block-heading" id="h-generate-new-project">Generate New Project</h3>



<p>As the first step, let&#8217;s navigate to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">https://start.spring.io/</a> page and generate a fresh Spring Boot project:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="530" src="http://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-1024x530.png" alt="Image presents the Spring Initializr page screenshot of a project setting that we will use to implement WebClient logic and test with WireMock." class="wp-image-9508754" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-1024x530.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-300x155.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-768x398.png 768w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project.png 1510w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>In general, the most important thing is that we will use Kotlin and Spring Reactive Web this time. Versions and metadata are totally up to you. </p>



<p>Of course, let&#8217;s click the &#8220;Generate&#8221; button, download the ZIP file, and import that to our IDE. Personally, I am using IntelliJ IDEA and if you would like to learn more about its setup, then check out my video on <a href="https://www.youtube.com/watch?v=npib90rBLE4" target="_blank" rel="noreferrer noopener">how to install IntelliJ for Kotlin development</a>.</p>



<h3 class="wp-block-heading" id="h-update-application-properties">Update Application Properties</h3>



<p>As the next step, let&#8217;s navigate to the <code>resources</code> package and update the <code>application.yaml</code> file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">api:
  github:
    key: ${GITHUB_API_TOKEN:123}
    url: ${GITHUB_API_BASE_URL:http://localhost:8090}
    version: ${GITHUB_API_VERSION:2022-11-28}</pre>



<p>Above we can see our custom properties. </p>



<p>We will source values for our GitHub key, URL, and version from environment variables. </p>



<p>Not only is this approach <strong>secure</strong> (hardcoding the key with token value would be the worst thing you could do), but also <strong>flexible</strong> (we can pass different values depending on the environment we are running our app in). </p>



<h3 class="wp-block-heading" id="h-webclient-configuration">WebClient Configuration</h3>



<p>Following, let&#8217;s configure a WebClient bean that we will use whenever we want to call the GitHub API. </p>



<p>Firstly, let&#8217;s add the <code>config</code> package and put the <code>GitHubApiProperties</code> data class in there:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@ConfigurationProperties("api.github")
data class GitHubApiProperties(
    val url: String,
    val key: String,
    val version: String,
)</pre>



<p>In my opinion, the <code>@ConfigurationProperties</code> annotation is the cleanest way to inject values from properties. As we can see above, the <strong>prefix</strong> and <strong>field names</strong> simply reflect the structure from the previous step. </p>



<p>With that done, let&#8217;s add the <code>GitHubApiConfig</code> in the same package: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Configuration
@EnableConfigurationProperties(GitHubApiProperties::class)
class GitHubApiConfig(
    private val gitHubApiProperties: GitHubApiProperties,
) {
    @Bean
    fun webClient(builder: WebClient.Builder): WebClient =
        builder
            .baseUrl(gitHubApiProperties.url)
            .defaultHeader("Authorization", "Bearer ${gitHubApiProperties.key}")
            .defaultHeader("X-GitHub-Api-Version", gitHubApiProperties.version)
            .defaultHeader("Accept", "application/vnd.github+json")
            .build()
}</pre>



<p>Quite a few things are happening here, so let&#8217;s dive into each one. </p>



<p>Firstly, we annotate the class with <code>@Configuration</code> and <code>@EnableConfigurationproperties</code>. The second annotation is necessary to for the previous steps to work. </p>



<p>Then, we inject the <code>GitHubApiProperties</code> instance and use its values to configure a new WebClient bean. This configuration is quite straightforward. We set the base URL and a bunch of headers that will be sent with <strong>every request</strong>: the authorization token, GH API version, and the accept header (those two are specific to the GitHub API, so let&#8217;s focus on that too much).  </p>



<h3 class="wp-block-heading" id="h-prepare-model-classes">Prepare Model Classes</h3>



<p>With that done, let&#8217;s prepare a bunch of data classes that we will need to deserialize JSON responses from the API (+ one exception class). If you would like to add other fields, please check out the documentation link I shared with you before. </p>



<p>As the first step, let&#8217;s add the <code>model</code> package and the <code>PageableGitHubResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class PageableGitHubResponse&lt;T>(
    val items: List&lt;T>,
    val hasMoreItems: Boolean,
)</pre>



<p>The API endpoints allow us to get only a chunk of data using the <code>page</code> and <code>per_page</code> parameters. And this class can be reused whenever we&#8217;re using a paginated endpoint. </p>



<p>As the next step, let&#8217;s implement the <code>GitHubRepoResponse</code> along with <code>GitHubOwnerResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class GitHubRepoResponse(
    val fork: Boolean,
    val name: String,
    val owner: GitHubOwnerResponse,
)

data class GitHubOwnerResponse(
    val login: String,
)</pre>



<p>As I mentioned at the beginning of this paragraph, we need those two in order to deserialize the actual JSON response payload. </p>



<p>Lastly, let&#8217;s add the <code>UpstreamApiException</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.http.HttpStatusCode

data class UpstreamApiException(
    val msg: String,
    val statusCode: HttpStatusCode,
) : RuntimeException(msg)</pre>



<p>This way, we will be able to throw custom exceptions. Such an exception could be handled later, for example with <a href="https://blog.codersee.com/exception-handling-with-restcontrolleradvice-and-exceptionhandler/" target="_blank" rel="noreferrer noopener">@RestControllerAdvice</a>.</p>



<h3 class="wp-block-heading" id="h-make-use-of-webclient">Make Use Of WebClient</h3>



<p>Finally, let&#8217;s combine all of that together. </p>



<p>So, let&#8217;s add the <code>api</code> package and implement the <code>GitHubApi</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Component
class GitHubApi(
  private val webClient: WebClient,
) {

  suspend fun listRepositoriesByUsername(
    username: String,
    page: Int,
    perPage: Int,
  ): PageableGitHubResponse&lt;GitHubRepoResponse>? =
    webClient.get()
      .uri("/users/$username/repos?page=$page&amp;per_page=$perPage")
      .awaitExchangeOrNull(::mapToPageableResponse)
}</pre>



<p>Don&#8217;t worry about the <code>mapToPageableResponse</code>, we will add it in a moment. </p>



<p>But before that, let&#8217;s analyze the above code. </p>



<p>Firstly, we annotate the class as the <code>@Component</code> to make it a Spring bean and inject the <code>WebClient</code> instance we configured earlier. We don&#8217;t have other WebClient instances, so there is no need to use <code>@Qualifier</code> or anything like that. </p>



<p>Following, we add the <code>listRepositoriesByUsername</code> function that lets us pass the <code>username</code> along with <code>page</code> and <code>perPage</code>. This way, we make this function reusable and allow the invoker to decide how to tackle the pagination. </p>



<p>Additionally, we mark it as a suspend function because the <code>awaitExchangeOrNull</code> is a suspend function. However, if you are interested in this topic, then I refer you to my other article about <a href="https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/" target="_blank" rel="noreferrer noopener">WebClient with coroutines</a>. </p>



<p>Lastly, let&#8217;s add the missing code:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private suspend inline fun &lt;reified T> mapToPageableResponse(clientResponse: ClientResponse): PageableGitHubResponse&lt;T>? {
  val hasNext = checkIfMorePagesToFetch(clientResponse)

  return when (val statusCode = clientResponse.statusCode()) {
    HttpStatus.OK ->
      PageableGitHubResponse(
        items = clientResponse.awaitBody&lt;List&lt;T>>(),
        hasMoreItems = hasNext,
      )

    HttpStatus.NOT_FOUND -> null

    else -> throw UpstreamApiException(
      msg = "GitHub API request failed.",
      statusCode = statusCode,
    )
  }
}

fun checkIfMorePagesToFetch(clientResponse: ClientResponse) =
  clientResponse.headers()
    .header("link")
    .firstOrNull()
    ?.contains("next")
    ?: false</pre>



<p>Long story short, the above code works as follows: </p>



<ol class="wp-block-list">
<li>Firstly, we take the response and check if there are more pages. How? Well, without going into details, GitHub API returns the <code>link</code> header. And if its value contains the <code>rel="next"</code>, it means that there are more pages to fetch. </li>



<li>Finally, we check the status code. If it is <code>200 OK</code>, we simply read the JSON value. When we get <code>404 Not Found</code>, we return null (we can expect this status code when we pass a non-existing username, and in my opinion, this case is not exceptional). And whenever we receive another status code, we simply throw the <code>UpstreamApiException</code>.</li>
</ol>



<h2 class="wp-block-heading" id="h-webclient-integration-testing-with-wiremock">WebClient Integration Testing With WireMock</h2>



<p>Excellent, at this point we have (almost) everything we need to test our WebClient implementation with WireMock. </p>



<p>We will see only a few example test cases that you will be able to easily adapt according to your needs:</p>



<ul class="wp-block-list">
<li>200 OK response with an empty list, 200 OK with items, and more pages, and 200 OK with items and no more pages to fetch</li>



<li>404 Not Found, </li>



<li>401 Unauthorized</li>
</ul>



<p>When you will be implementing your own integration tests, this is the moment when you should do the homework and analyze the API you are working with. What are the possible happy paths? How should your code behave in case of any error status code? What if we add some latency? And many, many more 🙂 </p>



<h3 class="wp-block-heading" id="h-additional-dependencies">Additional Dependencies</h3>



<p>At this point, we defined our test cases, so we are ready to get our hands dirty. </p>



<p>So, let&#8217;s start by adding the necessary dependencies: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock:4.1.1")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")</pre>



<p>As we can see, the first one- the Spring Cloud Contract WireMock module- allows us to use WireMock in our app, whereas the <code>kotlinx-coroutines-test</code> is necessary due to our coroutines WebClient implementation.</p>



<h3 class="wp-block-heading" id="h-add-expected-json-files">Add Expected JSON Files</h3>



<p>So, if we know what test cases we would like to test, we should prepare the example JSON API responses. </p>



<p>Personally, whenever working with WireMock I am a big fan of externalizing the expected JSON files. We put them inside the <code>/test/resources</code> directory and refer to them later in the tests. This way, we can improve the readability of the test classes. Of course, <strong>unless we want to prepare JSON responses dynamically</strong>. </p>



<p>In our case, we could simply invoke the GitHub API and persist responses for different cases. This way, we prepare the following files and put them inside the <code>/test/resources/responses/external/github</code>: </p>



<ul class="wp-block-list">
<li></li>
</ul>



<ul class="wp-block-list">
<li>list_github_repositories_200_OK_empty_list.json</li>



<li>list_github_repositories_200_OK_page_1.json</li>



<li>list_github_repositories_401_UNAUTHORIZED.json</li>



<li>list_github_repositories_404_NOT_FOUND.json</li>
</ul>



<p>You can find all of these files in my GH repo <a href="https://github.com/codersee-blog/integration-testing-spring-webflux-webclient-wiremock/tree/main/src/test/resources/responses/external/github" target="_blank" rel="noreferrer noopener">here</a>. </p>



<p>The example for <code>401 Unauthorized</code> looks as follows:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "message": "Bad credentials",
  "documentation_url": "https://docs.github.com/rest"
}</pre>



<h3 class="wp-block-heading" id="h-prepare-util-function">Prepare Util Function</h3>



<p>As the last thing before we implement our test class, let&#8217;s add the util function inside the <code>util</code> package (of course, in tests). </p>



<p>This function will be responsible for reading JSON files inside the test resources as String values:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.core.io.ClassPathResource

fun getResponseBodyAsString(path: String): String =
    ClassPathResource(path).getContentAsString(
        Charsets.UTF_8,
    )</pre>



<h3 class="wp-block-heading" id="h-implement-githubapitest-class">Implement GitHubApiTest Class</h3>



<p>Finally, let&#8217;s start implementing the <code>GitHubApiTest</code> class: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private const val TEST_KEY = "TEST_KEY"
private const val TEST_PORT = 8082
private const val TEST_VERSION = "2022-11-28"

@AutoConfigureWireMock(port = TEST_PORT)
@TestPropertySource(
  properties = [
    "api.github.url=http://localhost:${TEST_PORT}",
    "api.github.key=$TEST_KEY",
    "api.github.version=$TEST_VERSION",
  ],
)
@SpringBootTest(
  webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
class GitHubApiTest {
  @Autowired
  private lateinit var wireMockServer: WireMockServer

  @Autowired
  private lateinit var gitHubApi: GitHubApi

  private val page = 1
  private val perPage = 2
  private val username = UUID.randomUUID().toString()

}</pre>



<p>Conceptually, everything starts with the <code>@SpringBootTest</code> and <code>@AutoConfigureWireMock</code> annotations.</p>



<p>We use the <code>@SpringBootTest</code> annotation in Spring Boot integration tests. This way a new ApplicationContext will be created. In our case, we set the <code>webEnvironment</code> with <code>RANDOM_PORT</code> value, so that a reactive context listening on the random port is created. </p>



<p>Additionally, we combine that together with <code>@AutoConfigureWireMock</code> and fixed test port. This way a WireMock server will be started as a part of Application Context. </p>



<p>Lastly, we do 3 things: </p>



<ol class="wp-block-list">
<li>We use the <code>@TestPropertySource</code> to pass values for properties.</li>



<li>We inject the <code>WireMockServer</code> and <code>GitHubApi</code> instances that we will work with later. Please note that we can do that just like we would do with a standard, non-test, Spring component. </li>



<li>We prepare some dummy data for <code>page</code>, <code>perPage</code>, and <code>username</code> values we will need for testing. </li>
</ol>



<p>As a note of comment from my side- this &#8220;skeleton&#8221; is useful whenever working with WebClient and WireMock.</p>



<h3 class="wp-block-heading" id="h-test-404-not-found-api-response-case">Test 404 Not Found API Response Case</h3>



<p>Wonderful! </p>



<p>As the first test, let&#8217;s start with the <code>404 Not Found</code> case. Usually, we will start with happy path cases, but for learning purposes, this one is the shortest case 😉 </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 404 NOT FOUND response When fetching repository by username Then should return null`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.NOT_FOUND.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_404_NOT_FOUND.json"),
          ),
      ),
  )

  // When
  val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

  // Then
  Assertions.assertNull(result)
}</pre>



<p>We mark our test with the <code>@Test</code> (JUnit 5), specify the meaningful name for the test (with awesome Kotlin backticks), and wrap our test with <code>runTest</code>. The last one is necessary as we are working with coroutines and invoke the suspended function. Long story short, in JVM it will behave just like <code>runBlocking</code> (but it will skip delays). </p>



<p>Next comes the WireMock part. And to be even more specific- <strong>stubbing.</strong> </p>



<p>Stubbing is nothing else than &#8220;the ability to return canned HTTP responses for requests matching criteria&#8221; (from docs). Simply said, whenever an outgoing request that matches our criteria is made, a mocked response configured in <code>willReturn</code> is returned. </p>



<p>So, as we can see, the above logic will work only when: </p>



<ul class="wp-block-list">
<li>the GET request is made, </li>



<li>URL matches, </li>



<li>Headers sent have appropriate values </li>
</ul>



<p>And this part <strong>already tests our logic.</strong> Pretty neat, isn&#8217;t it? 🙂 </p>



<p>Lastly, we simply invoke the function and assert that <code>null</code> is returned. Which confirms that everything is working fine. </p>



<h3 class="wp-block-heading" id="h-test-401-unauthorized">Test 401 Unauthorized </h3>



<p>As we already started with error cases, let&#8217;s verify that whenever the endpoint returns <code>401 Unauthorized</code>, the <code>UpstreamApiException</code> is thrown:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 401 UAUTHORIZED response When fetching repository by username Then should throw UpstreamApiException`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.UNAUTHORIZED.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_401_UNAUTHORIZED.json"),
          ),
      ),
  )

  // When
  val exception = assertThrows&lt;UpstreamApiException> {
    gitHubApi.listRepositoriesByUsername(username, page, perPage)
  }

  // Then
  assertNotNull(exception)

  assertEquals("GitHub API request failed.", exception.msg)
  assertEquals(HttpStatus.UNAUTHORIZED, exception.statusCode)
}</pre>



<p>As we can see, the logic responsible for stubbing only slightly changed. </p>



<p>The interesting part here is the <code>assertThrows</code>, which not only makes sure that the exception of an appropriate type is thrown but also returns it. This way we can make additional assertions.</p>



<h3 class="wp-block-heading" id="h-verify-200-ok-with-empty-list">Verify 200 OK With Empty List</h3>



<p>Following, let&#8217;s cover the empty list case:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with empty list When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_empty_list.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items = emptyList&lt;GitHubRepoResponse>(),
        hasMoreItems = false,
      )

    assertEquals(expected, result)
  }</pre>



<p>This time, we assign the returned value to the <code>result</code> and compare that with the <code>expected</code>. </p>



<h3 class="wp-block-heading" id="h-check-200-ok-with-has-next-true">Check 200 OK With Has Next True</h3>



<p>Last before least, let&#8217;s make sure that we got the expected objects in a list and that the logic responsible for the <code>link</code> header check returns true:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next true`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="next","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_page_1.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items =
        listOf(
          GitHubRepoResponse(
            fork = false,
            name = "controlleradvice-vs-restcontrolleradvice",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
          GitHubRepoResponse(
            fork = false,
            name = "freecodecamp-spring-boot-kotlin-excel",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
        ),
        hasMoreItems = true,
      )

    assertEquals(expected, result)
  }</pre>



<h3 class="wp-block-heading" id="h-test-200-ok-with-has-next-false">Test 200 OK With Has Next False</h3>



<p>And finally, let&#8217;s double-check the flag logic:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_page_1.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items =
        listOf(
          GitHubRepoResponse(
            fork = false,
            name = "controlleradvice-vs-restcontrolleradvice",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
          GitHubRepoResponse(
            fork = false,
            name = "freecodecamp-spring-boot-kotlin-excel",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
        ),
        hasMoreItems = false,
      )

    assertEquals(expected, result)
  }</pre>



<h3 class="wp-block-heading" id="h-integration-tests-with-wiremock-and-webclient-summary">Integration Tests With WireMock And WebClient Summary</h3>



<p>And basically, that&#8217;s all for this tutorial on how to perform integration testing for Spring WebClient that invokes external REST APIs with WireMock and JUnit 5.</p>



<p>I hope you enjoyed this one and I recommend you check out my other articles related to <a href="https://blog.codersee.com/category/spring/">Spring Framework</a>.</p>
<p>The post <a href="https://blog.codersee.com/integration-tests-webclient-wiremock/">Integration Tests for WebClient with WireMock</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/integration-tests-webclient-wiremock/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Testing Micronaut Application in Kotlin</title>
		<link>https://blog.codersee.com/testing-micronaut-appplication-kotlin/</link>
					<comments>https://blog.codersee.com/testing-micronaut-appplication-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 10 Jan 2024 08:24:44 +0000</pubDate>
				<category><![CDATA[Micronaut]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[JUnit 5]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[REST Assured]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508620</guid>

					<description><![CDATA[<p>In this, practical tutorial, I will show you how to test a REST API created with Micronaut, Kotlin and MongoDB.</p>
<p>The post <a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/">Testing Micronaut Application in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to my next article, in which I will show you <strong>how to test a Micronaut application in Kotlin</strong>. </p>



<p>To be even more specific- we will work with an application that exposes a <strong>REST API</strong> and connects to <strong>MongoDB</strong>. If you would like to learn how to do that step-by-step, then you can check out <a href="https://blog.codersee.com/micronaut-with-mongodb-and-kotlin-revisited-2022/" target="_blank" rel="noreferrer noopener">my other article</a>. (But don&#8217;t worry, we will see its code snippets in this tutorial, too). </p>



<p>For testing, we&#8217;re going to use <strong>JUnit 5</strong>, <strong>MockK</strong>, <strong>REST Assured</strong>, as well as the <strong>@MicronautTest</strong> and <strong>Micronaut Test Resources</strong>.</p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FH-LbDsi4qKg%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-micronaut-project-overview">Micronaut Project Overview</h2>



<p>Let&#8217;s start everything by <strong>checking the project we will test today</strong>. Of course, we will focus on the most important parts, and for the full source code, I invite you to <a href="https://github.com/codersee-blog/kotlin-micronaut-testing" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<h3 class="wp-block-heading" id="h-application-properties">Application Properties</h3>



<p>In our project, we introduce 3 application properties files: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// application.properties
micronaut.application.name=micronaut-testing

// application-mongo.properties
mongodb.uri=mongodb://localhost:27017/example

// application-test.properties
mongodb.package-names=com.codersee.model</pre>



<p>The application name is the default property inserted when generating a new project. </p>



<p>Nevertheless, the two remaining are created on purpose. With their names- <code>*mongo</code>, <code>*test</code>&#8211; we instruct Micronaut that it should consider them only when <code>mongo</code> and <code>test</code> profiles are active. </p>



<p>But why this way? Well, to avoid two issues. </p>



<h3 class="wp-block-heading" id="h-mongodb-connection-issues-with-micronaut-test-resources">MongoDB Connection Issues with Micronaut Test Resources</h3>



<p>So, let&#8217;s start everything by explaining <strong>why we don&#8217;t put the Mongo URI inside the main properties</strong>.</p>



<p>Well, when we use the Micronaut Test Resources, MongoDB support will start a MongoDB container and provide the value of the <code>mongodb.uri</code> property. </p>



<p>Nevertheless, <strong>this won&#8217;t happen</strong> when we explicitly set the URI in the application properties. And that&#8217;s why we want to make this setting available <strong>only when we set the environment to <code>mongo</code>.</strong></p>



<h3 class="wp-block-heading" id="h-codeccachekey-issue">CodecCacheKey Issue</h3>



<p>Additionally, if we don&#8217;t specify explicitly the package name, where our model classes live, we&#8217;re going to end up with the following issue when running our Micronaut tests: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[io-executor-thread-1] ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: Can't find a codec for CodecCacheKey{clazz=class com.codersee.model.AppUser, types=null}.
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for CodecCacheKey{clazz=class com.codersee.model.AppUser, types=null}.
  at org.bson.internal.ProvidersCodecRegistry.lambda$get$0(ProvidersCodecRegistry.java:87)
  at java.base/java.util.Optional.orElseGet(Optional.java:364)
  at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:80)
  at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:50)
  at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:188)
...</pre>



<p>To me, looks like a bug. But thankfully, we can easily avoid that by putting that in the <code>application-test.properties</code> (which is used in tests by default).</p>



<h3 class="wp-block-heading" id="h-models">Models</h3>



<p>Following, we introduce a bunch of models:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Address.kt: 

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Serializable
@Serdeable.Deserializable
data class Address(
  val street: String,
  val city: String,
  val code: Int
)

// AppUser.kt:

import io.micronaut.data.annotation.GeneratedValue
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity

@MappedEntity
data class AppUser(
  @field:Id
  @field:GeneratedValue
  val id: String? = null,
  val firstName: String,
  val lastName: String,
  val email: String,
  val address: Address
)

// AppUserRequest.kt: 

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Deserializable
data class AppUserRequest(
  val firstName: String,
  val lastName: String,
  val email: String,
  val street: String,
  val city: String,
  val code: Int
)

// SearchRequest.kt:

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Deserializable
data class SearchRequest(val name: String)</pre>



<p>Those classes are either used to persist/retrieve data from MongoDB or when deserializing JSON payloads into the objects. </p>



<h3 class="wp-block-heading" id="h-repository">Repository</h3>



<p>Following, we have a repository layer responsible for communicating with our storage:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserRepository.kt: 

import com.codersee.model.AppUser
import io.micronaut.data.mongodb.annotation.MongoFindQuery
import io.micronaut.data.mongodb.annotation.MongoRepository
import io.micronaut.data.repository.CrudRepository

@MongoRepository
interface AppUserRepository : CrudRepository&lt;AppUser, String> {

  fun findByFirstNameEquals(firstName: String): List&lt;AppUser>

  @MongoFindQuery("{ firstName: { \$regex: :name}}")
  fun findByFirstNameLike(name: String): List&lt;AppUser>
}</pre>



<h3 class="wp-block-heading" id="h-service">Service</h3>



<p>Nextly, the service layer, where we inject the repository and encapsulate its methods: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserService.kt:

import com.codersee.model.Address
import com.codersee.model.AppUser
import com.codersee.model.AppUserRequest
import com.codersee.repository.AppUserRepository
import io.micronaut.http.HttpStatus
import io.micronaut.http.exceptions.HttpStatusException
import jakarta.inject.Singleton

@Singleton
class AppUserService(
  private val appUserRepository: AppUserRepository
) {

  fun create(userRequest: AppUserRequest): AppUser =
    appUserRepository.save(
      userRequest.toAppUserEntity()
    )

  fun findAll(): List&lt;AppUser> =
    appUserRepository
      .findAll()
      .toList()

  fun findById(id: String): AppUser =
    appUserRepository.findById(id)
      .orElseThrow { HttpStatusException(HttpStatus.NOT_FOUND, "User with id: $id was not found.") }

  fun update(id: String, updateRequest: AppUserRequest): AppUser {
    val foundUser = findById(id)

    val updatedEntity =
      updateRequest
        .toAppUserEntity()
        .copy(id = foundUser.id)

    return appUserRepository.update(updatedEntity)
  }

  fun deleteById(id: String) {
    val foundUser = findById(id)

    appUserRepository.delete(foundUser)
  }

  fun findByNameLike(name: String): List&lt;AppUser> =
    appUserRepository
      .findByFirstNameLike(name)

  private fun AppUserRequest.toAppUserEntity(): AppUser {
    val address = Address(
      street = this.street,
      city = this.city,
      code = this.code
    )

    return AppUser(
      id = null,
      firstName = this.firstName,
      lastName = this.lastName,
      email = this.email,
      address = address
    )
  }
}</pre>



<h3 class="wp-block-heading" id="h-controller">Controller </h3>



<p>And lastly, the place where we expose our REST endpoints: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserController.kt: 

import com.codersee.model.AppUserRequest
import com.codersee.service.AppUserService
import com.codersee.model.SearchRequest
import com.codersee.model.AppUser
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/users")
@ExecuteOn(TaskExecutors.IO)
class AppUserController(
  private val appUserService: AppUserService
) {

  @Get
  fun findAllUsers(): List&lt;AppUser> =
    appUserService.findAll()

  @Get("/{id}")
  fun findById(@PathVariable id: String): AppUser =
    appUserService.findById(id)

  @Post
  @Status(HttpStatus.CREATED)
  fun createUser(@Body request: AppUserRequest): AppUser =
    appUserService.create(request)

  @Post("/search")
  fun searchUsers(@Body searchRequest: SearchRequest): List&lt;AppUser> =
    appUserService.findByNameLike(
      name = searchRequest.name
    )

  @Put("/{id}")
  fun updateById(
    @PathVariable id: String,
    @Body request: AppUserRequest
  ): AppUser =
    appUserService.update(id, request)

  @Delete("/{id}")
  @Status(HttpStatus.NO_CONTENT)
  fun deleteById(@PathVariable id: String) =
    appUserService.deleteById(id)
}</pre>



<h2 class="wp-block-heading" id="h-simple-kotlin-testing-in-micronaut">Simple Kotlin Testing in Micronaut</h2>



<p>Excellent. At this point, we know what exactly we are going to test. </p>



<p>And as the first approach we&#8217;re going to see will be a plain, old unit test. </p>



<p>Why? </p>



<p>Because at the end of the day, <strong>that&#8217;s what we will be dealing with most of the time</strong>. </p>



<p>So with that said, let&#8217;s take a look at the example test: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">  import com.codersee.repository.AppUserRepository
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class AppUserServiceTest {

  private val appUserRepository = mockk&lt;AppUserRepository>()

  private val appUserService = AppUserService(appUserRepository)

  @Test
  fun `should return empty user list`() {
    every {
      appUserRepository.findAll()
    } returns emptyList()

    val result = appUserService.findAll()

    assertTrue(result.isEmpty())

    verify(exactly = 1) { appUserRepository.findAll() }
  }
}</pre>



<p>As we can see above, we simply mock (with MockK) the <em>AppUserRepository </em>and inject it into the <em>AppUserService </em>instance.</p>



<p>Then, we specify that all invocations of the <code>findAll</code> method must return the empty list (with <code>every</code>). </p>



<p>Lastly, we assert that the <code>findAll</code> method from our service returns an empty list (<code>assertTrue</code>) and that the <code>findAll</code> method from the repository was invoked only once (<code>verify</code>). </p>



<p>Nevertheless, we&#8217;re not going to spend too much here right now as this tutorial focuses on Micronaut testing in Kotlin. If you are interested in learning more about these tools, then let me know in the comments section 🙂 </p>



<h2 class="wp-block-heading" id="h-integration-testing-with-micronauttest">Integration Testing with @MicronautTest</h2>



<p>With all of that done, let&#8217;s see how the Micronaut framework helps us with testing and a few interesting cases that will help us in real life. </p>



<h3 class="wp-block-heading" id="h-what-is-a-micronauttest">What is a @MicronautTest? </h3>



<p>Let&#8217;s start everything by explaining what exactly is the <strong>@MicronautTest</strong>.</p>



<p>In simple words, it&#8217;s an annotation that we can put on the test class to mark it a Micronaut test (no sh&#8230; Sherlock 🙂 :</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.test.extensions.junit5.annotation.MicronautTest

@MicronautTest
class SomeTest {
  // tests
}</pre>



<p>In practice, it means that <strong>when we run a test with that annotation, it will run a real application</strong>. It will use internal Micronaut features with no mocking. So at this point, we can clearly see that this will be the right solution for <strong>integration testing.</strong></p>



<p>Moreover, this annotation can be used not only with <strong>JUnit 5</strong>, but also with <strong>Spock</strong>, <strong>Kotest</strong>, and <strong>Kotest 5</strong>. </p>



<h3 class="wp-block-heading" id="h-micronauttest-configuration-options">@MicronautTest Configuration Options </h3>



<p>The @MicronautTest allows us to configure a few properties, like the environments we want to run our test with, the packages to scan, or whether the automatic transaction wrapping should be enabled, or not:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.test.extensions.junit5.annotation.MicronautTest

@MicronautTest(
  environments = ["env-1, env-2"],
  packages = ["com.codersee.somepackage"],
  transactional = false
)
class SomeTest {
  // tests
}</pre>



<p>For the full list of options, I highly encourage you to check out the docs (we can do that for instance in IntelliJ IDEA by clicking on the annotation with the left mouse button when we keep the left ctrl pressed). </p>



<h3 class="wp-block-heading" id="h-what-are-micronaut-test-resources">What Are Micronaut Test Resources? </h3>



<p>At the beginning of this tutorial, I mentioned the Micronaut Test Resources and I would like to make sure that we are on the same page with them. </p>



<p>So basically, Micronaute Test Resources <strong>integrates seamlessly with Testcontainers to provide throwaway containers for testing</strong>. But, we need to remember that Testcontainers <strong>require Docker-API compatible container runtime</strong> (in simple words- either Docker installed locally, or the Testcontainers Cloud). </p>



<p>In our case, we would like to do integration tests that require MongoDB. We could make use of the real database (which sometimes may be the case for you), provide some H2 database (not the best approach), or integrate Testcontainers. Thankfully, we don&#8217;t need to do that and the only thing we need is the appropriate import in our project: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">plugins {
    id("io.micronaut.test-resources") version "4.2.1"
}</pre>



<p>As a reminder, I want to emphasize that <strong>test resources will be used only when the <code>datasources.default.url</code> is missing.</strong> (and that&#8217;s why we removed the URI for MongoDB from tests). <strong> </strong> </p>



<h2 class="wp-block-heading" id="h-integration-tests-with-rest-assured">Integration Tests With REST Assured</h2>



<p>With all of that said, let&#8217;s combine everything together and test our Kotlin Micronaut application with JUnit 5 and REST Assured. </p>



<p>To do so, we must remember to add the necessary import: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">testImplementation("io.micronaut.test:micronaut-test-rest-assured")</pre>



<h3 class="wp-block-heading" id="h-verify-status-codes-and-headers">Verify Status Codes and Headers</h3>



<p>Let&#8217;s start with a simple request to the <code>GET /users</code> endpoint: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithoutMocking {

  @Test
  fun `should return 200 OK on GET users`(spec: RequestSpecification) {
    spec
      .`when`()
      .get("/users")
      .then()
      .statusCode(200)
      .header("Content-Type", "application/json")
  }

}</pre>



<p>As I mentioned previously- by default, with @MicronautTest the real application is started. Moreover, nothing here is mocked, so the Mongo test container delivered by the Micronaut Test Resources is used.</p>



<p>Thanks to the dedicated module we added, we can inject the <code>RequestSpecification</code> into our tests and easily validate our endpoint. </p>



<p>In this case, we <strong>perform a GET request</strong> and verify that the <strong>response status code is 200 OK</strong> and the response <strong><code>Content-Type</code> header value is <code>application/json</code></strong>. </p>



<h3 class="wp-block-heading" id="h-verify-404-not-found">Verify 404 Not Found</h3>



<p>Similarly, we can verify that no entry is present in the database: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `should return 404 Not Found on GET user by ID`(spec: RequestSpecification) {
  spec
    .`when`()
    .get("/users/123123123123123123121231")
    .then()
    .statusCode(404)
}</pre>



<p>This test will be also good proof that entries inserted to MongoDB in other tests do not affect other tests. </p>



<h3 class="wp-block-heading" id="h-extract-and-json-array-in-rest-assured">Extract and JSON Array in REST Assured</h3>



<p>Nextly, let&#8217;s take a look at the more complicated example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `should create a user`(spec: RequestSpecification) {
  val request = AppUserRequest(
    firstName = "Piotr",
    lastName = "Wolak",
    email = "contact@codersee.com",
    street = "Street",
    city = "City",
    code = 123,
  )

  spec
    .`when`()
    .contentType(ContentType.JSON)
    .body(request)
    .post("/users")
    .then()
    .statusCode(201)

  val list = spec
    .`when`()
    .get("/users")
    .then()
    .statusCode(200)
    .body("size()", `is`(1))
    .extract()
    .`as`(object : TypeRef&lt;List&lt;AppUser>>() {})

  assertEquals(1, list.size)

  val createdUser = list.first()

  assertEquals(request.firstName, createdUser.firstName)
  assertEquals(request.street, createdUser.address.street)
}</pre>



<p>This time, we do a bunch of more interesting things. </p>



<p>Firstly, we make the <strong>POST request </strong>with a request body and <strong>verify that 201 Creates is returned</strong>. </p>



<p>After that, we call the <code>GET /users</code> endpoint to get a list of users. When we get the response, we <strong>verify that the JSON array size is equal to 1</strong> (with the <code>.body("size()", is(1))</code> call). Lastly, we <strong>make use of the extract().`as</strong>` to convert the payload into the List of AppUser instances. Thanks to that we can easily perform assertions with JUnit 5 functions. </p>



<h3 class="wp-block-heading" id="h-add-kotlin-utils-for-rest-assured">Add Kotlin Utils For REST Assured</h3>



<p>As you probably know, <code>when</code> and <code>as</code> are keywords in Kotlin and that&#8217;s why <strong>we had to use backticks (&#8220;)</strong>.</p>



<p>And to make our lives easier, let&#8217;s add the <code>util</code> package in <code>test</code> and create the <code>TestUtil.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.restassured.common.mapper.TypeRef
import io.restassured.response.ValidatableResponse
import io.restassured.specification.RequestSpecification


fun RequestSpecification.whenever(): RequestSpecification {
  return this.`when`()
}

fun &lt;T> ValidatableResponse.extractAs(clazz: Class&lt;T>) =
  this.extract()
    .`as`(clazz)

fun &lt;T> ValidatableResponse.extractAs(typeRef: TypeRef&lt;T>) =
  this.extract()
    .`as`(typeRef)</pre>



<p>As a result, from now on we can simply use the <em>whenever() </em>instead of <em>`when`()</em> and <em>extractAs() </em>instead of <em>extract().`</em>as`(). </p>



<h2 class="wp-block-heading" id="h-reference-the-server-context">Reference The Server / Context </h2>



<p>As the next step, let&#8217;s take a look at how to <strong>reference the server</strong> or <strong>current application context</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class InjectingExample{

  @Inject
  private lateinit var context: ApplicationContext

  @Inject
  private lateinit var server: EmbeddedServer

  // tests
}</pre>



<p>Sometimes it can be useful, and as we can see, we can simply inject them using the <strong>@Inject</strong> annotation. </p>



<h2 class="wp-block-heading" id="h-conditionally-run-tests">Conditionally Run Tests</h2>



<p>After that, let&#8217;s see <strong>how we can skip tests execution.</strong> </p>



<p>Why would we need that? </p>



<p>Well, integration tests sometimes can take a lot of time, and the bigger our project becomes, the bigger the chance they become annoying. And because of that, generally, it&#8217;s a good approach to somehow separate them from the faster unit tests. </p>



<p> When testing in Micronaut, we can easily achieve that using the <strong><em>@Requires</em></strong> annotation:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.context.annotation.Requires
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

@MicronautTest
@Requires(env = ["integration-test"])
class SomeIntegrationTest {

  @Test
  fun `should perform integration test`() {
    assertTrue(false)
  }

}</pre>



<p>The <strong><em>@MicronautTest</em></strong> annotation <strong>turns tests into beans</strong>. And that&#8217;s why we can use the <strong>@Requires</strong>, just like we would do with a &#8220;standard&#8221; bean.</p>



<p>As a result, tests inside the <code>SomeIntegrationTest</code> will run only, when the <code>integration-test</code> the environment is active. (And we can set that for example by setting the environment variable- <code>MICRONAUT_ENVIRONMENTS=integration-test</code>). </p>



<h2 class="wp-block-heading" id="h-testing-micronaut-with-mockk-mocks">Testing Micronaut With MockK Mocks</h2>



<p>As the last thing in our tutorial about testing Micronaut with Kotlin, let&#8217;s take a look at <strong>how we can mock things using the MockK</strong>.</p>



<p>Could we use Mockito? Yes. However, I find MockK better for Kotlin (DSLs &lt;3 ).</p>



<p>In order to mock a bean in Micronaut, the only thing we need to do is <strong>annotate the method/inner class with the @MockBean annotation</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithMocking {

  private val repo: AppUserRepository = mockk&lt;AppUserRepository>()

  @MockBean(AppUserRepository::class)
  fun appUserRepository(): AppUserRepository = repo
}</pre>



<p>As we can see, we introduce the <code>AppUserRepository</code> mock as the <code>repo</code> property, so that later, we will be able to refer to it easily in our test cases. Additionally, we add the function annotated with <code>@MockBean</code>, thus informing Micronaut that the <code>AppUserRepository</code> is the type we want to replace with our mock. </p>



<p>Moreover, thanks to this approach <strong>this mock will be limited only to tests defined in this class</strong>. So we can &#8220;rest assured&#8221; (hue hue) it won&#8217;t interfere with other classes. </p>



<p>As a result, our tests will look, as follows: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithMocking {

  private val repo: AppUserRepository = mockk&lt;AppUserRepository>()

  @Test
  fun `should return 200 OK on GET users`(spec: RequestSpecification) {
    every {
      repo.findAll()
    } returns emptyList()

    spec
      .whenever()
      .get("/users")
      .then()
      .statusCode(200)
      .header("Content-Type", "application/json")
  }

  @Test
  fun `should return user by ID`(spec: RequestSpecification) {
    val foundUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.findById("123")
    } returns Optional.of(foundUser)

    spec
      .whenever()
      .get("/users/123")
      .then()
      .statusCode(200)
      .body("id", equalTo("123"))
      .body("firstName", equalTo("Piotr"))
      .body("address.street", equalTo("street"))
      .body("address.code", equalTo(123))
  }

  @Test
  fun `should return user by ID and verify with extract`(spec: RequestSpecification) {
    val foundUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.findById("123")
    } returns Optional.of(foundUser)

    val extracted = spec
      .whenever()
      .get("/users/123")
      .then()
      .statusCode(200)
      .extractAs(AppUser::class.java)

    assertEquals(foundUser, extracted)
  }

  @Test
  fun `should create a user`(spec: RequestSpecification) {
    val request = AppUserRequest(
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      street = "Street",
      city = "City",
      code = 123,
    )

    val createdUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.save(any())
    } returns createdUser

    val extracted = spec
      .whenever()
      .contentType(ContentType.JSON)
      .body(request)
      .post("/users")
      .then()
      .statusCode(201)
      .extractAs(AppUser::class.java)

    assertEquals(createdUser, extracted)
  }

  @MockBean(AppUserRepository::class)
  fun appUserRepository(): AppUserRepository = repo
}</pre>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on <strong>how to perform testing in Micronaut with Kotlin.</strong></p>



<p>Together, we&#8217;ve discovered a bunch of interesting things that I hope will be useful in your projects. </p>



<p>If you would like to see the codebase for this tutorial, then check out <a href="https://github.com/codersee-blog/kotlin-micronaut-testing" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. Or, if you are interested in learning more about Micronaut, then check out <a href="https://blog.codersee.com/category/micronaut/">my other posts</a>. </p>



<p>Let me know your thoughts in the comments section below! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/">Testing Micronaut Application in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/testing-micronaut-appplication-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-02-27 03:25:35 by W3 Total Cache
-->