Skip to content

A Kotlin library that leverages existing mocks to create Pact contract files, using Mockk or Mockito

License

Notifications You must be signed in to change notification settings

ludorival/pact-jvm-mock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pact JVM Mock

Build status GitHub release (latest by date)

A Kotlin library that leverages existing mocks to create Pact contract files, using the popular mocking libraries Mockk and Mockito.

Motivation

Pact is a powerful tool for ensuring the compatibility of microservices. It allows you to define contracts between your services and test them in isolation.

However, writing these contracts can be time-consuming and repetitive. This is where pact-jvm-mock comes in. It automatically generates Pact contracts from your existing Mockk mocks or Mockito mocks, saving you time and reducing the risk of human error.

Features

  • Automatically generate Pact contracts from existing Mockk mocks or Mockito mocks
  • Supports all common mock interactions, such as method calls and property accesses
  • Compatible with Spring Rest Template client and fully extensible
  • Easy to integrate with your existing testing workflow

Getting Started in 5 minutes

Setup

To get started with pact-jvm-mock, you'll need to add the library to your project. You can do this by adding the following dependency to your build.gradle file:

Gradle

testImplementation "io.github.ludorival:pact-jvm-mockk-spring:$pactJvmMockVersion"

Or if you are using Maven:

Maven

<!-- For intercepting Mockk calls -->
<dependency>
    <groupId>io.github.ludorival</groupId>
    <artifactId>pact-jvm-mock-mockk</artifactId>
    <version>${pactJvmMockVersion}</version>
    <scope>test</scope>
</dependency>

<!-- For intercepting Mockito calls -->
<dependency>
    <groupId>io.github.ludorival</groupId>
    <artifactId>pact-jvm-mock-mockito</artifactId>
    <version>${pactJvmMockVersion}</version>
    <scope>test</scope>
</dependency>

<!-- For intercepting Spring RestTemplate calls -->
<dependency>
    <groupId>io.github.ludorival</groupId>
    <artifactId>pact-jvm-mock-spring</artifactId>
    <version>${pactJvmMockVersion}</version>
    <scope>test</scope>
</dependency>

Configure the pacts

Next, you'll need to configure the library to use your existing Mockk mocks.

For example, let's say you want to leverage existing mock of Spring RestTemplate. Create a Kotlin object (or use an existing one) implementing PactConfiguration. Here is a minimal example:

import io.github.ludorival.pactjvm.mock.PactConfiguration
import io.github.ludorival.pactjvm.mock.spring.SpringRestTemplateMockAdapter

object MyServicePactConfig : PactConfiguration("my-service", SpringRestTemplateMockAdapter())

Extend your tests files

Then, to start writing contract, you have to extend your test classes where you need to record the interactions with your providers. Like that

import io.github.ludorival.pactjvm.mock.mockk.PactConsumer

@PactConsumer(MyServicePactConfig::class)
class ShoppingServiceClientTest 

Migrate lightly your existing mocks

Finally, you can use the library to generate your Pact contracts.

Using with Mockk (Kotlin)

The library is fully compatible with your existing Mockk code. You just need to replace all your every calls with uponReceiving:

// Your existing Mockk code
every {
    restTemplate.getForEntity(match<String> { it.contains("user-service") }, UserProfile::class.java)
} returns ResponseEntity.ok(USER_PROFILE)

// Simply becomes
uponReceiving {
    restTemplate.getForEntity(match<String> { it.contains("user-service") }, UserProfile::class.java)
} returns ResponseEntity.ok(USER_PROFILE)

All your existing Mockk matchers and features continue to work exactly the same way. The library supports:

  • Static responses with returns
  • Dynamic responses with answers
  • Multiple responses with andThen
  • Exception throwing with throws
  • Coroutines with coAnswers

This migration can be easily done with a Replace All in your IDE, replacing every with uponReceiving.

Using with Mockito (Java)

To use pact-jvm-mock with Mockito in Java, simply replace all your Mockito.when calls with PactMockito.uponReceiving:

import static io.github.ludorival.pactjvm.mock.mockito.PactMockito.uponReceiving;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

// Simple response
uponReceiving(restTemplate.getForEntity(any(String.class), eq(UserProfile.class)))
    .thenReturn(ResponseEntity.ok(USER_PROFILE));

// With error response
uponReceiving(restTemplate.postForEntity(any(URI.class), any(), eq(UserProfile.class)))
    .withDescription("Create user profile - validation error")
    .given((builder, call) -> builder.state("Invalid user data", Collections.emptyMap()))
    .thenThrow(HttpClientErrorException.BadRequest.create(
                    HttpStatus.BAD_REQUEST,
                    errorMessage,
                    new HttpHeaders(),
                    errorMessage.getBytes(StandardCharsets.UTF_8),
                    StandardCharsets.UTF_8
            ));

You can also add optional configurations like descriptions, provider states, and matching rules:

uponReceiving(restTemplate.exchange(
        any(URI.class),
        eq(HttpMethod.GET),
        any(),
        any(ParameterizedTypeReference.class)
    ))
    .withDescription("List users")
    .given((builder, call) -> builder.state("Users exist", Collections.emptyMap()))
    .matchingRequest(rules -> rules.header("Authorization", new RegexMatcher("Bearer .*")))
    .matchingResponse(rules -> rules.body("[*].id", TypeMatcher.INSTANCE))
    .thenReturn(ResponseEntity.ok(Arrays.asList(USER_1, USER_2)));

Note: Don't forget to configure your test class with the @PactConsumer annotation:

@PactConsumer(MyPactConfiguration.class)
public class MyTest {
    public static class MyPactConfiguration extends PactConfiguration {
        
        public MyPactConfiguration() {
            super("my-consumer", new SpringRestTemplateMockAdapter());
        }
    }
    // ... test methods
}

That's it !!

Run your tests, and you should see the generated pact files in your src/test/resources/pacts.

More configurations

Set a description

By default, the description is building from the current test name.You can set a description for each interaction using either withDescription or as part of the response chain:

uponReceiving {
    restTemplate.getForEntity(match<String> { it.contains("user-service") }, UserProfile::class.java)
} withDescription { "get the user profile" } returns ResponseEntity.ok(USER_PROFILE)

Set provider states

The provider state refers to the state of the API or service that is being tested. You can specify provider states using the given method:

uponReceiving {
    restTemplate.getForEntity(match<String> { it.contains("user-service") }, UserProfile::class.java)
} given { 
    state("The user has a preferred shopping list") 
} returns ResponseEntity.ok(USER_PROFILE)

Configure matching rules

You can specify matching rules for both requests and responses using matchingRequest and matchingResponse:

uponReceiving {
    restTemplate.exchange(
        match<URI> { it.path.contains("user-service") },
        HttpMethod.GET,
        any(),
        any<ParameterizedTypeReference<List<User>>>()
    )
} matchingRequest {
    header("Authorization", RegexMatcher("Bearer .*"))
} matchingResponse {
    body("[*].id", TypeMatcher)
} returns ResponseEntity.ok(listOf(USER_1, USER_2))

In this example:

  • The request matching rule ensures the Authorization header matches the pattern "Bearer" followed by any string
  • The response matching rule specifies that each user ID in the array should match by type rather than exact value

Client error

pact-jvm-mock offers also a way to record Http errors thanks to the throws anError() method:

uponReceiving {
    restTemplate.postForEntity(
        match<URI> { it.path.contains("shopping-service") },
        any(),
        eq(ShoppingList::class.java)
    ) given {
        state("The request should return a 400 Bad request") 
    } throws anError(ResponseEntity.badRequest().body("The title contains unexpected character")) 

Configure custom JSON ObjectMapper

You can specify a custom ObjectMapper for serializing request/response bodies for specific providers. This is useful when you need special serialization handling, like custom date formats or naming strategies.

// MyServicePactConfig.kt
object MyServicePactConfig : PactConfiguration("my-service", SpringRestTemplateMockAdapter({providerName ->
    Jackson2ObjectMapperBuilder()
        .propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
        .serializerByType(
            LocalDate::class.java,
            serializerAsDefault<LocalDate>("2023-01-01")
        ).build()
})) {

} 

Make your contract deterministic

To make your contract deterministic, you will need to provide a custom serializer for the type you want to be invariant.

For example, let's say you have a date which will be generated at each test, you can pass a custom value for determineConsumerFromUrl

// MyServicePactConfig.kt
object MyServicePactConfig : PactConfiguration("my-service", SpringRestTemplateMockAdapter({providerName ->
    Jackson2ObjectMapperBuilder()
        .propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
        .serializerByType(
            LocalDate::class.java,
            serializerAsDefault<LocalDate>("2023-01-01")
        ).build()
})) {

    override fun isDeterministic() = true // <-- force to be deterministic
} 

Change the pact directory

By default, the generated pacts are stored in src/test/resources/pacts. You can configure that in the pact options:

// MyServicePactConfig.kt
object MyServicePactConfig : PactConfiguration("my-service") {

    ...
    override fun getPactDirectory() = "my-own-directory"
} 

Contributing

pact-jvm-mock is an open-source project and contributions are welcome! If you're interested in contributing, please check out the contributing guidelines.

License

pact-jvm-mock is licensed under the MIT License.

About

A Kotlin library that leverages existing mocks to create Pact contract files, using Mockk or Mockito

Resources

License

Stars

Watchers

Forks

Packages

No packages published