BaseWiremock
is an abstract base class that simplifies the management of WireMock servers for integration testing. It provides a wrapper around WireMockServer
with convenient initialization and management methods, making it easier to mock HTTP services in your tests.
Here's a basic example of how to create and use a WireMock server in your tests:
class MyApiTest {
// Create a WireMock instance with custom stubs
private val mockServer = object : BaseWiremock({}) {
fun stubGetUserProfile() {
mock.stubFor(
WireMock.get("/api/profile")
.willReturn(WireMock.okJson("""{"name": "John Doe"}"""))
)
}
}
@Test
fun `test API interaction`() {
// Setup the stub
mockServer.stubGetUserProfile()
// Make your API call
val response = yourApiClient.getUserProfile()
// Verify the response
assertThat(response.name).isEqualTo("John Doe")
}
}
BaseWiremock
offers several ways to initialize the WireMock server:
- Using Configuration Block
private val mockServer = object : BaseWiremock({
it.dynamicPort() // Use random available port
it.disableRequestJournal() // Disable request journal if needed
}) {
// Your stub methods here
}
- Using WireMockConfiguration
private val mockServer = object : BaseWiremock(
WireMockConfiguration.options()
.port(8080)
.disableRequestJournal()
) {
// Your stub methods here
}
- Using Existing WireMockServer
private val mockServer = object : BaseWiremock(
WireMockServer(WireMockConfiguration.options().port(8080))
) {
// Your stub methods here
}
- Dynamic Port Allocation
// Get the port number assigned to the server
val port = mockServer.port()
- Stub Management
// Reset specific stub
mockServer.resetStub(stubMapping)
// Reset all stubs
mockServer.resetAllStubs()
- Request Verification
// Verify no unmatched requests
mockServer.verifyNoUnmatchedRequests()
class MyApiTest {
private val mockServer = object : BaseWiremock({}) {
// Group related stubs together
fun stubUserEndpoints() {
stubGetUserProfile()
stubUpdateUserProfile()
stubDeleteUserProfile()
}
private fun stubGetUserProfile() {
mock.stubFor(
WireMock.get("/api/profile")
.willReturn(WireMock.okJson("""{"name": "John"}"""))
)
}
private fun stubUpdateUserProfile() {
mock.stubFor(
WireMock.put("/api/profile")
.willReturn(WireMock.ok())
)
}
private fun stubDeleteUserProfile() {
mock.stubFor(
WireMock.delete("/api/profile")
.willReturn(WireMock.ok())
)
}
}
}
private val mockServer = object : BaseWiremock({}) {
fun stubWithRequestMatching() {
mock.stubFor(
WireMock.post("/api/data")
.withHeader("Content-Type", WireMock.equalTo("application/json"))
.withRequestBody(WireMock.matchingJsonPath("$.type", WireMock.equalTo("test")))
.willReturn(WireMock.ok())
)
}
}
private val mockServer = object : BaseWiremock({}) {
fun stubVariousResponses() {
// JSON Response
mock.stubFor(
WireMock.get("/api/data")
.willReturn(WireMock.okJson("""{"status": "success"}"""))
)
// Error Response
mock.stubFor(
WireMock.get("/api/error")
.willReturn(
WireMock.aResponse()
.withStatus(500)
.withBody("Internal Server Error")
)
)
// Response with Headers
mock.stubFor(
WireMock.get("/api/protected")
.willReturn(
WireMock.ok()
.withHeader("Authorization", "Bearer token")
)
)
}
}
class IntegrationTest {
private val mockServer = object : BaseWiremock({
it.dynamicPort()
}) {
fun stubExternalService() {
mock.stubFor(
WireMock.get("/external/api")
.willReturn(WireMock.ok("Success"))
)
}
}
private val client = HttpClient.newHttpClient()
@Test
fun `test external service integration`() {
// Setup
mockServer.stubExternalService()
// Execute
val request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:${mockServer.port()}/external/api"))
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
// Verify
assertThat(response.body()).isEqualTo("Success")
}
}
class ErrorHandlingTest {
private val mockServer = object : BaseWiremock({}) {
fun stubServiceUnavailable() {
mock.stubFor(
WireMock.get("/api/service")
.willReturn(
WireMock.serviceUnavailable()
.withBody("Service Unavailable")
)
)
}
}
@Test
fun `should handle service unavailable`() {
mockServer.stubServiceUnavailable()
// Test your error handling logic
}
}
- Unmatched Requests
@Test
fun `test with verification`() {
try {
// Your test code
mockServer.verifyNoUnmatchedRequests()
} catch (e: VerificationException) {
println("Unmatched requests found: ${e.message}")
throw e
}
}
- Port Conflicts
// Always use dynamic ports in tests to avoid conflicts
private val mockServer = object : BaseWiremock({
it.dynamicPort()
}) {
// Your stubs
}
- Memory Usage
// Disable request journal for performance
private val mockServer = object : BaseWiremock({
it.disableRequestJournal()
}) {
// Your stubs
}
Key configuration options available:
private val mockServer = object : BaseWiremock({
it.dynamicPort() // Use random port
it.port(8080) // Use specific port
it.disableRequestJournal() // Disable request recording
it.maxRequestJournalEntries(100) // Limit journal entries
it.extensionScanningEnabled(true) // Enable extensions
}) {
// Your stubs
}
Instead of creating mock instances per test class, define them as singleton objects to improve performance and resource usage:
// Define mock as a singleton object
object UserServiceMock : BaseWiremock({
it.dynamicPort()
}) {
fun stubGetUserProfile(userId: String) {
mock.stubFor(
WireMock.get("/api/users/$userId")
.willReturn(WireMock.okJson("""{"id": "$userId", "name": "John Doe"}"""))
)
}
fun stubUpdateUserProfile(userId: String) {
mock.stubFor(
WireMock.put("/api/users/$userId")
.willReturn(WireMock.ok())
)
}
}
// Usage in tests
class UserServiceTest {
@Test
fun `test get user profile`() {
val userId = "user123"
UserServiceMock.stubGetUserProfile(userId)
val response = yourApiClient.getUserProfile(userId)
assertThat(response.id).isEqualTo(userId)
}
}
fun stubSuccessfulPayment() { /* ... */ }
fun stubFailedPayment() { /* ... */ }
fun stubPaymentTimeout() { /* ... */ }
private val mockServer = object : BaseWiremock({}) {
// Authentication stubs
fun stubAuthEndpoints() { /* ... */ }
// User management stubs
fun stubUserEndpoints() { /* ... */ }
// Payment stubs
fun stubPaymentEndpoints() { /* ... */ }
}
Design mock methods to accept unique identifiers to allow parallel test execution:
object PaymentServiceMock : BaseWiremock({}) {
// Bad: Fixed endpoint that may conflict in parallel tests
fun stubPaymentProcessing() {
mock.stubFor(
WireMock.post("/api/payments")
.willReturn(WireMock.ok())
)
}
// Good: Accepts unique transaction ID for parallel execution
fun stubPaymentProcessing(transactionId: String) {
mock.stubFor(
WireMock.post("/api/payments/$transactionId")
.willReturn(WireMock.ok())
)
}
}
class PaymentTests {
@Test
fun `test successful payment`() {
val transactionId = UUID.randomUUID().toString()
PaymentServiceMock.stubPaymentProcessing(transactionId)
val result = paymentService.processPayment(transactionId)
assertThat(result.status).isEqualTo("SUCCESS")
}
}
object OrderServiceMock : BaseWiremock({}) {
// Accept unique identifiers for parallel test execution
fun stubOrderFlow(orderId: String, userId: String) {
stubCreateOrder(orderId, userId)
stubGetOrderStatus(orderId)
stubUpdateOrder(orderId)
}
private fun stubCreateOrder(orderId: String, userId: String) {
mock.stubFor(
WireMock.post("/api/orders")
.withRequestBody(WireMock.matchingJsonPath("$.userId", WireMock.equalTo(userId)))
.willReturn(
WireMock.okJson("""{"orderId": "$orderId", "status": "CREATED"}""")
)
)
}
private fun stubGetOrderStatus(orderId: String) {
mock.stubFor(
WireMock.get("/api/orders/$orderId/status")
.willReturn(WireMock.okJson("""{"status": "IN_PROGRESS"}"""))
)
}
private fun stubUpdateOrder(orderId: String) {
mock.stubFor(
WireMock.put("/api/orders/$orderId")
.willReturn(WireMock.ok())
)
}
}
// Usage in parallel tests
class OrderProcessingTests {
@Test
fun `test order creation and processing`() {
val orderId = UUID.randomUUID().toString()
val userId = UUID.randomUUID().toString()
OrderServiceMock.stubOrderFlow(orderId, userId)
val result = orderService.createAndProcessOrder(userId)
assertThat(result.orderId).isEqualTo(orderId)
}
}
object UserProfileMock : BaseWiremock({}) {
fun stubUserProfile(
userId: String,
userData: UserData = UserData(userId, "John Doe", "[email protected]")
) {
mock.stubFor(
WireMock.get("/api/users/$userId")
.willReturn(
WireMock.okJson(
"""
{
"id": "${userData.id}",
"name": "${userData.name}",
"email": "${userData.email}"
}
""".trimIndent()
)
)
)
}
data class UserData(
val id: String,
val name: String,
val email: String
)
}
class UserTests {
@Test
fun `test custom user profile`() {
val userId = UUID.randomUUID().toString()
val customData = UserProfileMock.UserData(
id = userId,
name = "Jane Doe",
email = "[email protected]"
)
UserProfileMock.stubUserProfile(userId, customData)
val result = userService.getUserProfile(userId)
assertThat(result.name).isEqualTo("Jane Doe")
}
}
When your application interacts with multiple services, organize related mocks together:
// Group related mocks in an object
object TestMocks {
val userService = UserServiceMock
val paymentService = PaymentServiceMock
val orderService = OrderServiceMock
// Setup common test scenarios
fun stubSuccessfulOrderFlow(userId: String, orderId: String) {
userService.stubGetUserProfile(userId)
orderService.stubOrderFlow(orderId, userId)
paymentService.stubPaymentProcessing(orderId)
}
}
class IntegrationTests {
@Test
fun `test complete order flow`() {
val userId = UUID.randomUUID().toString()
val orderId = UUID.randomUUID().toString()
TestMocks.stubSuccessfulOrderFlow(userId, orderId)
val result = orderProcessingService.processOrder(userId)
assertThat(result.status).isEqualTo("SUCCESS")
}
}
Always verify there are no unmatched requests after each test to:
Detect missing or incorrectly configured stubs Identify unexpected service behavior Ensure all expected requests were properly mocked Catch integration issues early These best practices ensure that:
- Tests can run in parallel without conflicts
- Resources are efficiently managed through singleton instances
- Mock definitions are reusable across test classes
- Test data can be customized while maintaining consistent behavior
- Related mocks can be organized and managed together
- Common test scenarios can be easily set up
class ApiTest {
private val mockServer = MyServiceMock
@AfterEach
fun verifyMocks() {
mockServer.verifyNoUnmatchedRequests()
}
@Test
fun `test api call`() {
mockServer.stubEndpoint("data")
service.getData() // If this makes an unexpected call, verifyMocks() will fail
}
}
abstract class BaseApiTest {
@AfterEach
fun verifyAllMocks() {
// Verify all singleton mocks
TestMocks.userService.verifyNoUnmatchedRequests()
TestMocks.orderService.verifyNoUnmatchedRequests()
TestMocks.paymentService.verifyNoUnmatchedRequests()
}
}
class UserServiceTest : BaseApiTest() {
@Test
fun `test user profile`() {
val userId = UUID.randomUUID().toString()
TestMocks.userService.stubGetUserProfile(userId)
userService.getUserProfile(userId)
// verifyAllMocks() will run automatically after the test
}
}