How to Write Unit Tests and Integration Tests for CoApi Client Interfaces
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 (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 (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.
// 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:
ApplicationContextRunnerloads only the configuration classes under test, avoiding the overhead of@SpringBootTest. - Mock external dependencies: Provide mocked
LoadBalancedExchangeFilterFunctionbeans to satisfy load-balancer requirements without starting a service registry. - Assert bean presence: Verify that
ReactiveHttpExchangeAdapterFactoryand the specific client interface (e.g.,GitHubApiClient) are registered as singleton beans.
The official repository provides a comprehensive example in CoApiAutoConfigurationTest.kt (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.
// 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
0to avoid conflicts, then inject the actual port into thebaseUrlproperty (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
StepVerifierfrom Project Reactor to assert onFluxorMonoreturn types without blocking threads.
Summary
- CoApi client interfaces are Spring-managed beans created by
CoApiAutoConfigurationbased on@CoApiannotations parsed byCoApiDefinition. - Unit tests use
ApplicationContextRunnerto verify bean registration and configuration parsing without network calls, as demonstrated inCoApiAutoConfigurationTest.kt. - Integration tests use
@SpringBootTestwith WireMock to validate the full request/response cycle through the generated client proxy. - Key source files for testing logic include
CoApiDefinition.ktfor URL resolution logic andCoApiAutoConfiguration.ktfor 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.
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 in the spring-boot-starter module, demonstrating ApplicationContextRunner usage, and CoApiDefinitionTest.kt in the spring module, showing how annotation attributes convert to definition objects. The example client GitHubApiClient.kt in the test sources illustrates a typical interface for integration testing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →