# How to Write Unit Tests and Integration Tests for CoApi Client Interfaces

> Learn to write unit and integration tests for CoApi client interfaces. Verify bean registration with ApplicationContextRunner and test proxies against real endpoints using WireMock.

- Repository: [Ahoo Wang/coapi](https://github.com/ahoo-wang/coapi)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Unit tests for CoApi client interfaces verify Spring bean registration using `ApplicationContextRunner`, while integration tests exercise the generated proxy against real HTTP endpoints using tools like WireMock.**

CoApi client interfaces are plain Kotlin interfaces annotated with `@CoApi` that the Spring Boot starter automatically converts into fully configured HTTP clients. The `ahoo-wang/coapi` repository provides a clean separation between configuration logic and runtime behavior, allowing you to test your client setup without starting a full application context and validate HTTP mappings against mock servers.

## Understanding the CoApi Bean Registration Architecture

When the Spring Boot application starts, `CoApiAutoConfiguration` scans for interfaces annotated with `@CoApi` and registers two beans for each client:

- **`<clientName>.HttpClient`**: The underlying HTTP client (WebClient or RestClient) used to invoke the remote service.
- **`<clientName>.CoApi`**: The dynamic proxy implementation of the annotated interface that forwards calls to the HTTP client.

The core parsing logic lives in [`CoApiDefinition.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinition.kt) ([`spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiDefinition.kt`](https://github.com/ahoo-wang/coapi/blob/main/spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiDefinition.kt)), which resolves base URLs (including `lb://` load-balanced URLs) from the Spring `Environment` and determines load-balancing requirements. The auto-configuration trigger resides in [`CoApiAutoConfiguration.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiAutoConfiguration.kt) ([`spring-boot-starter/src/main/kotlin/me/ahoo/coapi/spring/boot/starter/CoApiAutoConfiguration.kt`](https://github.com/ahoo-wang/coapi/blob/main/spring-boot-starter/src/main/kotlin/me/ahoo/coapi/spring/boot/starter/CoApiAutoConfiguration.kt)).

Because Spring manages these beans entirely through the application context, you can validate your configuration layer independently from HTTP runtime behavior.

## Writing Unit Tests for CoApi Auto-Configuration

Unit tests for CoApi client interfaces target the configuration layer. They verify that given specific property values and optional dependency beans, the `CoApiAutoConfiguration` correctly registers the expected client beans without requiring network I/O.

Use `ApplicationContextRunner` from `spring-boot-test` to isolate the configuration classes and inject mock dependencies such as `LoadBalancedExchangeFilterFunction`.

```kotlin
// src/test/kotlin/me/ahoo/coapi/example/ClientBeanTest.kt
package me.ahoo.coapi.example

import io.mockk.mockk
import me.ahoo.coapi.example.consumer.client.GitHubApiClient
import me.ahoo.coapi.spring.EnableCoApi
import me.ahoo.coapi.spring.boot.starter.CoApiAutoConfiguration
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancedExchangeFilterFunction

class ClientBeanTest {

    private val contextRunner = ApplicationContextRunner()
        .withUserConfiguration(EnableCoApiConfiguration::class.java)
        .withUserConfiguration(CoApiAutoConfiguration::class.java)
        .withBean(
            "loadBalancerExchangeFilterFunction",
            LoadBalancedExchangeFilterFunction::class.java
        ) { mockk() }

    @Test
    fun `auto-configuration creates reactive CoApi bean`() {
        contextRunner
            .withPropertyValues("github.url=https://api.github.com")
            .run { ctx ->
                // Verify the HTTP exchange adapter factory is registered
                ctx.assertThat { 
                    hasSingleBean(me.ahoo.coapi.spring.client.reactive.ReactiveHttpExchangeAdapterFactory::class.java) 
                }
                // Verify the generated client proxy is registered
                ctx.assertThat { hasSingleBean(GitHubApiClient::class.java) }
            }
    }
}

```

**Key testing strategies:**

- **Isolate the context**: `ApplicationContextRunner` loads only the configuration classes under test, avoiding the overhead of `@SpringBootTest`.
- **Mock external dependencies**: Provide mocked `LoadBalancedExchangeFilterFunction` beans to satisfy load-balancer requirements without starting a service registry.
- **Assert bean presence**: Verify that `ReactiveHttpExchangeAdapterFactory` and the specific client interface (e.g., `GitHubApiClient`) are registered as singleton beans.

The official repository provides a comprehensive example in [`CoApiAutoConfigurationTest.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiAutoConfigurationTest.kt) ([`spring-boot-starter/src/test/kotlin/me/ahoo/coapi/spring/boot/starter/CoApiAutoConfigurationTest.kt`](https://github.com/ahoo-wang/coapi/blob/main/spring-boot-starter/src/test/kotlin/me/ahoo/coapi/spring/boot/starter/CoApiAutoConfigurationTest.kt)), which demonstrates asserting bean types and configuration properties.

## Writing Integration Tests for CoApi Clients

Integration tests validate that the generated CoApi proxy correctly serializes requests and deserializes responses. These tests require a running Spring context and an HTTP server to receive the actual network calls.

Use `@SpringBootTest` combined with an in-memory mock server like **WireMock** to stub remote service responses.

```kotlin
// src/test/kotlin/me/ahoo/coapi/example/ClientIntegrationTest.kt
package me.ahoo.coapi.example

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock.*
import me.ahoo.coapi.example.consumer.client.GitHubApiClient
import me.ahoo.coapi.example.consumer.client.Issue
import me.ahoo.coapi.spring.EnableCoApi
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import reactor.test.StepVerifier

@SpringBootTest(
    classes = [EnableCoApiConfiguration::class],
    properties = [
        // Inject the dynamic WireMock port into the client configuration
        "github.url=http://localhost:\${wiremock.port}"
    ]
)
class ClientIntegrationTest {

    companion object {
        val wireMock = WireMockServer(0)   // 0 selects a random free port

        @BeforeAll @JvmStatic fun startWireMock() = wireMock.start()
        @AfterAll @JvmStatic fun stopWireMock() = wireMock.stop()
    }

    @Autowired lateinit var githubApiClient: GitHubApiClient

    @Test
    fun `GitHubApiClient fetches issues`() {
        // Configure the mock server response
        wireMock.stubFor(
            get(urlPathEqualTo("/repos/owner/repo/issues"))
                .willReturn(
                    aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody("[{\"id\":1,\"title\":\"Issue one\"}]")
                )
        )

        // Invoke the generated CoApi client proxy
        val flux = githubApiClient.getIssue("owner", "repo")

        // Verify the reactive stream completes with expected data
        StepVerifier.create(flux)
            .expectNextMatches { it.title == "Issue one" }
            .verifyComplete()
    }
}

```

**Integration testing best practices:**

- **Dynamic port allocation**: Start WireMock on port `0` to avoid conflicts, then inject the actual port into the `baseUrl` property (e.g., `github.url`).
- **Test the proxy layer**: Autowire the client interface directly (e.g., `GitHubApiClient`) to ensure the CoApi-generated proxy correctly maps method arguments to HTTP parameters and response bodies to domain objects.
- **Reactive verification**: Use `StepVerifier` from Project Reactor to assert on `Flux` or `Mono` return types without blocking threads.

## Summary

- **CoApi client interfaces** are Spring-managed beans created by `CoApiAutoConfiguration` based on `@CoApi` annotations parsed by `CoApiDefinition`.
- **Unit tests** use `ApplicationContextRunner` to verify bean registration and configuration parsing without network calls, as demonstrated in [`CoApiAutoConfigurationTest.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiAutoConfigurationTest.kt).
- **Integration tests** use `@SpringBootTest` with WireMock to validate the full request/response cycle through the generated client proxy.
- **Key source files** for testing logic include [`CoApiDefinition.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinition.kt) for URL resolution logic and [`CoApiAutoConfiguration.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiAutoConfiguration.kt) for bean registration behavior.

## Frequently Asked Questions

### How do I test a CoApi client that uses load-balanced URLs?

Provide a mocked `LoadBalancedExchangeFilterFunction` bean in your `ApplicationContextRunner` for unit tests, or include a `LoadBalancerClient` configuration in your integration test context. The `CoApiAutoConfiguration` checks for the presence of load-balancer filters when resolving `lb://` URLs in [`CoApiDefinition.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinition.kt).

### Can I write integration tests without starting a full Spring Boot context?

Yes, use `@WebFluxTest` or `@RestClientTest` if you only need to test the web layer. However, you must explicitly import `CoApiAutoConfiguration` and your client interfaces, as these sliced tests do not auto-scan for `@CoApi` annotated beans.

### What is the difference between the `HttpClient` and `CoApi` beans?

The `HttpClient` bean (e.g., `github.HttpClient`) is the underlying WebClient or RestClient instance configured with base URLs and filters. The `CoApi` bean (e.g., `github.CoApi`) is the JDK dynamic proxy implementing your annotated interface, which delegates method calls to the `HttpClient`. Unit tests typically verify the `CoApi` proxy bean is registered, while integration tests exercise its method implementations.

### Where can I find official examples of CoApi tests?

The `ahoo-wang/coapi` repository contains [`CoApiAutoConfigurationTest.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiAutoConfigurationTest.kt) in the `spring-boot-starter` module, demonstrating `ApplicationContextRunner` usage, and [`CoApiDefinitionTest.kt`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinitionTest.kt) in the `spring` module, showing how annotation attributes convert to definition objects. The example client [`GitHubApiClient.kt`](https://github.com/ahoo-wang/coapi/blob/main/GitHubApiClient.kt) in the test sources illustrates a typical interface for integration testing.