purepigeon

tools & utilities

testing utils

overview

Streamline Java / Spring Boot unit and integration tests by eliminating boilerplate code related to test resource building / loading and assertions; focus on the core logic of the tests and keep them free from clutter.

This documentation is intended as supplementary material to the javadocs that come with Testing Utils.

key features

  • Automated test resource loading

    Automatically loads JSON test resources based on a convention-driven approach.
  • Type conversion

    Converts test resources to and from Java objects.
  • JSON assertions

    Simplifies the comparison of actual results with expected JSON payloads.
  • JUnit 5 integration

    Provides a JUnit 5 extension.
  • Suite and test case organization

    Organizes test resources logically using @Suite and @TestCase annotations.
  • Boilerplate reduction

    Minimizes repetitive code for reading input data and verifying expected outputs.
  • Misc utilities

    Various useful utilities like @FixedClock

quick start

Start here to be up & running as soon as possible - this section introduces Testing Utils usage in two scenarios: with Spring Boot; and without a framework.

spring boot

1. add dependency

<dependency>
    <groupId>com.purepigeon.test</groupId>
    <artifactId>testing-utils</artifactId>
    <version>2.0.0</version>
    <scope>test</scope>
</dependency>

Furthermore, a JSON library is also required. Testing Utils implementations are provided for Jackson, Gson, and JSON-B - see the implementations section for more info.

To proceed with this guide, include the following dependency as well:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jackson</artifactId>
</dependency>

2. prepare sample domain & service

To facilitate the rest of this guide, let's prepare a sample service that will be tested:

public record SampleRequest(String id, String payload) {}

public record SampleResponse(String result) {}
@Service
public class SampleService {
    
    public SampleResponse processSampleRequest(SampleRequest request) {
        String result = String.format("%s_%s_processed", request.id(), request.payload());

        return new SampleResponse(result);
    }

}

3. prepare & annotate test class

Add the @WithTestingUtils annotation to the test class - this registers a TestingUtils bean in the test application context and enables the bundled JUnit 5 extension.

When using Spring Boot's defaults, the Testing Utils autoconfiguration will register a TestingUtils bean that uses Jackson's ObjectMapper under the hood.

@WithTestingUtils
@SpringBootTest(classes = SampleService.class)
class SampleServiceTest {

    @Autowired
    private TestingUtils testingUtils;

    @Autowired
    private SampleService sampleService;

}

4. create a test

Testing Utils resolves a testCase argument for all test methods (which is by default, the method's name).

It can then be used inside the test method with the methods that TestingUtils provides:

@WithTestingUtils
@SpringBootTest(classes = SampleService.class)
class SampleServiceTest {

    @Autowired
    private TestingUtils testingUtils;

    @Autowired
    private SampleService sampleService;

    @Test
    void testProcessSampleRequest(String testCase) {
        // given
        SampleRequest request = testingUtils.readInputObject(testCase, SampleRequest.class);

        // when
        SampleResult result = sampleService.processSampleRequest(request);

        // then
        testingUtils.assertObject(testCase, result);
    }
}

Where:

  • readInputObject loads an input test resource, deserialized into an instance of SampleRequest - refer to the resource loading section to learn more

  • assertObject serializes the result, loads a corresponding SampleResponse.json from the expected test resources, then asserts that the expected JSON matches the result JSON, using JSONAssert - refer to the assertions section to learn more

5. add test resources

The test case above needs two resources: an input SampleRequest, and an expected SampleResponse.

First, create a directory in test resources that aligns with the test class above:

src/test/resources/SampleServiceTest/testProcessSampleRequest

From here, two directories are needed: input and expected, to contain input resources and expected resources for this test case, respectively.

Create src/test/resources/SampleServiceTest/testProcessSampleRequest/input/SampleRequest.json:

{
    "id": "sampleId1",
    "payload": "samplePayload1"
}

Create src/test/resources/SampleServiceTest/testProcessSampleRequest/expected/SampleResponse.json:

{
    "result": "sampleId1_samplePayload1_processed"
}

6. run test

With the test resources added, the test can now be executed, concluding this quick start guide.

We have explored the basic and default functionalities of Testing Utils with Spring Boot; read on to find out more.

no framework

1. add dependency

<dependency>
    <groupId>com.purepigeon.test</groupId>
    <artifactId>testing-utils</artifactId>
    <version>2.0.0</version>
    <scope>test</scope>
</dependency>

Furthermore, a JSON library is also required. Testing Utils implementations are provided for Jackson, Gson, and JSON-B - see the implementations section for more info.

To proceed with this guide, include Jackson:

<dependency>
    <groupId>tools.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>3.1.4</version>
</dependency>

2. prepare sample domain & processor

To facilitate the rest of this guide, let's prepare sample classes that will be tested:

public record SampleRequest(String id, String payload) {}

public record SampleResponse(String result) {}
public class SampleProcessor {
    
    public SampleResponse processSampleRequest(SampleRequest request) {
        String result = String.format("%s_%s_processed", request.id(), request.payload());

        return new SampleResponse(result);
    }

}

3. prepare & annotate test class

Add the @WithTestingUtils annotation to the test class - this enables the bundled JUnit 5 extension.

Since we can't rely on Spring Boot dependency injection here, Testing Utils needs to be initialized manually. Let's use the JacksonTestingUtils implementation, since we included the Jackson dependency earlier:

@WithTestingUtils
class SampleProcessorTest {

    private final TestingUtils testingUtils = new JacksonTestingUtils(new ObjectMapper());

    private final SampleProcessor sampleProcessor = new SampleProcessor();

}

4. create a test

Testing Utils resolves a testCase argument for all test methods (which is by default, the method's name).

It can then be used inside the test method with the methods that TestingUtils provides:

@WithTestingUtils
class SampleProcessorTest {

    private final TestingUtils testingUtils = new JacksonTestingUtils(new ObjectMapper());

    private final SampleProcessor sampleProcessor = new SampleProcessor();

    @Test
    void testProcessSampleRequest(String testCase) {
        // given
        SampleRequest request = testingUtils.readInputObject(testCase, SampleRequest.class);

        // when
        SampleResult result = sampleProcessor.processSampleRequest(request);

        // then
        testingUtils.assertObject(testCase, result);
    }
}

Where:

  • readInputObject loads an input test resource, deserialized into an instance of SampleRequest - refer to the resource loading section to learn more

  • assertObject serializes the result, loads a corresponding SampleResponse.json from the expected test resources, then asserts that the expected JSON matches the result JSON, using JSONAssert - refer to the assertions section to learn more

5. add test resources

The test case above needs two resources: an input SampleRequest, and an expected SampleResponse.

First, create a directory in test resources that aligns with the test class above:

src/test/resources/SampleProcessorTest/testProcessSampleRequest

From here, two directories are needed: input and expected, to contain input resources and expected resources for this test case, respectively.

Create src/test/resources/SampleProcessorTest/testProcessSampleRequest/input/SampleRequest.json:

{
    "id": "sampleId1",
    "payload": "samplePayload1"
}

Create src/test/resources/SampleProcessorTest/testProcessSampleRequest/expected/SampleResponse.json:

{
    "result": "sampleId1_samplePayload1_processed"
}

6. run test

With the test resources added, the test can now be executed, concluding this quick start guide.

We have explored the basic and default functionalities of Testing Utils without a framework; read on to find out more.

resource loading

Testing Utils provides facilities for loading JSON resources, potentially deserialized into a target class. The convention used to locate resources is based on the following path structure:

classpath:/{suite}/{testCase}/{artifactType}/{artifactName}

Let's break down each part of this path:

  • suite

    Represents a logical grouping of tests, typically related to a specific class or component. By default, this is the name of the test class. It can be customized using the @Suite annotation.
  • testCase

    Identifies a specific test method / scenario within the suite. By default, this is the name of the test method, and is provided as a String testCase argument to the test method. It can be overridden using the @TestCase annotation.
  • artifactType

    Indicates the type of test resource ("input" or "expected"). Most TestingUtils methods determine this value based on the method name (e.g., readInputObject implies "input").
  • artifactName

    The name of the test resource file. This can be inferred from the class type provided to the TestingUtils method (e.g., "SampleRequest.json" for readInputObject(testCase, SampleRequest.class)), or provided directly in other respective methods (e.g., "Override.json" for readInputObject(testCase, "Override.json", SampleRequest.class)).

assertions

Testing Utils provides convenience methods for asserting on the content of Java objects against prepared expected resources.

Let's take a snippet from the Spring Boot quickstart guide:

@Test
void testProcessSampleRequest(String testCase) {
    // given
    SampleRequest request = testingUtils.readInputObject(testCase, SampleRequest.class);

    // when
    SampleResult result = sampleService.processSampleRequest(request);

    // then
    testingUtils.assertObject(testCase, result);
}

Let's break down what the assertObject(testCase, result) method call does:

  1. Converts the result argument into JSON

  2. Finds & reads a JSON resource from the test case's expected resources directory - the file name is inferred from the type of the result argument, in this case, SampleResult.json

  3. Asserts that the two JSONs are equal using JSONAssert (Using mode JSONCompareMode.NON_EXTENSIBLE)

There are other variations of the assertObject method as well that give more fine-grained control over the above steps - refer to their javadocs for more information.

implementations

Testing Utils provides implementations (and autoconfiguration) for the following JSON libraries:

  • Jackson 3 - ObjectMapper

  • Jackson 2 - ObjectMapper (deprecated, will be removed when Spring Boot 4 drops backsupport for Jackson 2)

  • Gson - Gson

  • JSON-B - Jsonb

This library does not transitively provide any of these dependencies - it is up to you to pull in the JSON library of your choice, and Testing Utils will adapt accordingly.

Be aware with Spring Boot - the Jackson implementation of Testing Utils is marked as a @Primary bean in the provided autoconfiguration.

An implementation with no backing JSON library is also provided by the SimpleTestingUtils class - for use cases that require no POJO mapping.

extending

Testing Utils can easily be extended to e.g. accommodate a JSON library that isn't supported by default.

We are open to considering adding new implementations to the library itself - send us an email or open a discussion on GitHub.

If you prefer to do this yourself, the easiest and most compatible way to venture down this route is to extend the AbstractTestingUtils class that already implements suite handling - which is likely to be the same, regardless of the JSON library used.

annotations

Usage of testing-utils is facilitated by the annotations described in this section.

@WithTestingUtils

Apply this annotation to a test class to easily opt into testing-utils functionalities.

When applied, it enables the testing-utils JUnit 5 extension.

If Spring is used, it also registers a TestingUtils bean in the application context, which can then be autowired into the given test class:

// ...
@SpringBootTest
@WithTestingUtils
class ExampleTest {
    
    @Autowired
    private TestingUtils testingUtils;

    // ...
}

Usage without Spring is slightly different, and requires manual instantiation:

// ...
@WithTestingUtils
class ExampleTest {
    
    private final TestingUtils testingUtils = new JacksonTestingUtils(new ObjectMapper());

    // ...
}

@Suite

A class-level annotation used to prefix / override the suite value for the given test class.

By default, suite is set to the simple class name of the given test class:

// ...
@SpringBootTest
@WithTestingUtils
class ExampleTest {
    
    @Autowired
    private TestingUtils testingUtils;

    @Test
    void testSuite(String testCase) {
        assertEquals("ExampleTest", testingUtils.getSuite());
    }
}

Applying this annotation with a value prefixes the default suite value:

// ...
@SpringBootTest
@WithTestingUtils
@Suite("service/example")
class ExampleTest {
    
    @Autowired
    private TestingUtils testingUtils;

    @Test
    void testSuite(String testCase) {
        assertEquals("service/example/ExampleTest", testingUtils.getSuite());
    }
}

Set the appendClassName parameter to false to disable appending the test class name to the annotation value, allowing full customization of the suite:

// ...
@SpringBootTest
@WithTestingUtils
@Suite(value = "service/example", appendClassName = false)
class ExampleTest {
    
    @Autowired
    private TestingUtils testingUtils;

    @Test
    void testSuite(String testCase) {
        assertEquals("service/example", testingUtils.getSuite());
    }
}

@TestCase

A method-level annotation used to override the String testCase argument resolved for a given test method.

By default, the test case is equal to the test method name:

// ...
@SpringBootTest
@WithTestingUtils
class ExampleTest {

    @Test
    void testMethod(String testCase) {
        assertEquals("testMethod", testCase);
    }
}

Applying this annotation completely overrides the value resolved for the argument:

// ...
@SpringBootTest
@WithTestingUtils
class ExampleTest {

    @Test
    @TestCase("testMethodCustomized")
    void testMethod(String testCase) {
        assertEquals("testMethodCustomized", testCase);
    }
}

When it comes to parameterized tests like the one below, testCase should be renamed so as not to clash with the parameter resolver provided by Testing Utils:

// ...
@SpringBootTest
@WithTestingUtils
class ExampleTest {

    @ParameterizedTest
    @CsvSource({"testMethod_1", "testMethod_2"})
    void testMethod(String testCaseName) {
        // ...
    }
}

@FixedClock

Class- and method-level annotation used to register a mock Clock bean in the application context that uses a fixed instant (by default, FixedClock.DEFAULT_TIME):

// ...
@FixedClock
@SpringBootTest
@WithTestingUtils
class ExampleTest {
    // ...
}

The instant the clock is fixed to can be overridden via the annotation value:

// ...
@FixedClock("2026-08-23T12:01:05.271Z")
@SpringBootTest
@WithTestingUtils
class ExampleTest {
    // ...
}

When applied on the method-level, the clock will temporarily shift to the fixed instant defined in the method annotation value, reverting to the global instant after the test method has executed:

// ...
@FixedClock
@SpringBootTest
@WithTestingUtils
class ExampleTest {
    
    @Test
    @FixedClock("2026-08-23T12:01:05.271Z")
    void testMethod(String testCase) {
        
    }
}

It can be used without Spring as well. In this case, a clock field is required, and it will be adjusted based on the annotations via reflection:

@FixedClock
@WithTestingUtils
class ExampleTest {    

    private final Clock clock = mock();

    // ...
}

mock web server integration

Testing utils now includes a module that provides convenient integration between testing utils and the okhttp3 mockwebserver. It is also able to work with or without Spring Boot.

The module is available under the following coordinates:

<dependency>
    <groupId>com.purepigeon.test</groupId>
    <artifactId>testing-utils-mockwebserver</artifactId>
    <version>2.0.0</version>
    <scope>test</scope>
</dependency>

usage

Annotate your test class with @WithMockWebServer - this will enable the JUnit extension (and if you're using Spring Boot, it will also register a MockWebServerSupport bean in your application context):

@SpringBootTest
@WithTestingUtils
@WithMockWebServer
class SampleServiceTest {

    @Autowired
    private TestingUtils testingUtils;

    @Autowired
    private MockWebServerSupport mockWebServerSupport;

}

With this setup, a mock web server will be started before, and stopped after each test method. By default, it will be started on a random unused port, which can be acquired via MockWebServerSupport::port() after the mock web server is started. It is also possible to set a fixed port via the port parameter of @WithMockWebServer.

For simpler use cases, the repeatable @EnqueueResponse annotation can be used to automatically read - by default, an input - artifact from test resources, and enqueue it in the mock web server:

@Test
@EnqueueResponse(SampleResponse.class)
void testMethod(String testCase) {
    // ...
}

The @EnqueueResponse annotation has some more options to customize behavior, see the @EnqueueResponse section for details.

For more fine-grained usage, MockWebServerSupport has various methods to enqueue responses, so the annotation-driven declarative approach is not the only option.

Once the mock web server has received a request, they can be verified using MockWebServerSupport:

@Test
@EnqueueResponse(SampleResponse.class)
void testMethod(String testCase) {
    // ...

    RecordedRequest request = mockWebServerSupport.assertRequest(testCase, SampleRequest.class);

    // ...
}

This will assert that the recorded request body matches the SampleRequest.json expected artifact, loaded from test resources. The recorded request is then returned for potential further assertions (e.g. on http method, headers, etc).

By default, all test methods in this class will have a mock web server started / stopped for them. If you don't want the mock web server to be started for a specific test method, you can opt it out with the @MockWebServerlessTest annotation:

@Test
@MockWebServerlessTest
void testMethod(String testCase) {
    // ...
}

interface

The heart of this module is the MockWebServerSupport interface - a wrapper for the okhttp3 mock web server that provides convenience methods not unlike the TestingUtils interface.

Here's a rundown of the methods within:

  • start(), start(port), and stop() to control mock web server state (done by the JUnit extension automatically, if test class is annotated with @WithMockWebServer)

  • port() to get the port that the mock web server was started on

  • Variants of enqueueInputResource(...) to easily enqueue artifacts from 'input' test resources as responses

  • Similarly, variants of enqueueExpectedResource(...) to enqueue artifacts from 'expected' test resources as responses

  • enqueueResource(...), where the user has full control over what response to enqueue (plus, the status and headers)

  • takeRequest() and takeRequest(timeoutMs) to directly get a recorded request for verification

  • Variants of assertRequest(...) to automatically use an expected resource to verify the recorded request body before returning the recorded request for potential further verification

  • unwrap() to get direct access to the underlying mock web server

Instead of the declarative approach of enqueuing responses with @EnqueueResponse, this interface can be used to achieve the same functionality programatically. The following snippets are equivalent in terms of behavior:

@Test
@EnqueueResponse(SampleResponse.class)
void testMethod(String testCase) {
    // ... send request to mock web server

    RecordedRequest recordedRequest = mockWebServerSupport.assertRequest(testCase, SampleRequest.class);

    // ... further verification
}
@Test
void testMethod(String testCase) {
    mockWebServerSupport.enqueueInputResource(testCase, SampleResponse.class);

    // ... send request to mock web server

    RecordedRequest recordedRequest = mockWebServerSupport.assertRequest(testCase, SampleRequest.class);

    // ... further verification
}

annotations

This module also provides some annotations to customize behavior, described below.

@WithMockWebServer

Apply this annotation to a test class (alongside @WithTestingUtils) to opt into the functionalities of this module.

This will enable the JUnit extension and the Spring Boot autoconfiguration that registers a MockWebServerSupport bean in the application context (if Spring Boot is on the classpath).

The JUnit extension will start and stop a mock web server before and after - respectively - each test method in the annotated class. It also handles the declarative directives given via the @EnqueueResponse and @MockWebServerlessTest annotations.

@SpringBootTest
@WithTestingUtils
@WithMockWebServer
class SampleServiceTest {

    @Autowired
    private TestingUtils testingUtils;

    @Autowired
    private MockWebServerSupport mockWebServerSupport;

}

By default, the mock web server will use a random unused port. Although not ideal for automated tests, it's also possible to define a fixed port:

// ...
@WithTestingUtils
@WithMockWebServer(9001)
class SampleServiceTest {
    // ...
}

@EnqueueResponse

This repeatable annotation can be used to declaratively enqueue responses (from test resources) in the mock web server.

@Test
@EnqueueResponse(SampleResponse.class)
void testMethod(String testCase) {
    // ...
}

By default, the artifact will be looked for in the 'input' subfolder of the test case resources - however, this can be customized with the artifactType parameter:

@Test
@EnqueueResponse(value = SampleResponse.class, artifactType = DefaultArtifactType.EXPECTED)
void testMethod(String testCase) {
    // ...
}

The artifact name is resolved from the provided class in the value parameter. If there is no pojo available (or needed) to represent the response, the artifact name can be provided as well, instead of the class:

@Test
@EnqueueResponse(artifactName = "SampleResponse.json")
void testMethod(String testCase) {
    // ...
}

To customize the response status (the default is 200 OK):

@Test
@EnqueueResponse(value = SampleResponse.class, status = 200)
void testMethod(String testCase) {
    // ...
}

To customize the Content-Type header value in the response (the default is application/json, provide blank string to not include a Content-Type header):

@Test
@EnqueueResponse(artifactName = "TextResponse.txt", contentType = "text/plain")
void testMethod(String testCase) {
    // ...
}

The annotation is repeatable, responses will be enqueued in the order they are declared on the test method:

@Test
@EnqueueResponse(SampleResponse.class)
@EnqueueResponse(artifactName = "AnotherSampleResponse.json")
void testMethod(String testCase) {
    // ...
}

For more fine-grained usage, the programmatic approach can be taken by not using this annotation and setting up enqueued responses directly with MockWebServerSupport.

@MockWebServerlessTest

This annotation can be used to opt-out of starting a mock web server for a given test method:

@Test
@MockWebServerlessTest
void testMethod(String testCase) {
    // ...
}

changelog

2.0.0

Jul 18, 2026

Changes in testing-utils module

  • Breaking change: removed deprecated ArtifactType enum

  • Breaking change: removed deprecated methods of the TestingUtils interface

  • Breaking change: the JacksonTestingUtils implementation now uses Jackson 3 - a new implementation called Jackson2TestingUtils keeps support for Jackson 2 - for as long as Spring Boot 4 does

  • Added a simple implementation (SimpleTestingUtils) of the TestingUtils interface with no backing JSON library

  • Extended autoconfiguration to include new implementations: Jackson2TestingUtils and SimpleTestingUtils

  • Added Jspecify annotations in various classes

  • Added detection and appropriate error message for the cases where maven compiler plugin is used without the -parameters flag

  • Updated javadocs

  • Extended unit tests

  • Alignments to new modules of spring boot 4

  • Bumped dependencies

    • Spring boot dependencies 3.5.8 4.1.0

    • Okhttp3 dependencies 5.3.2 5.4.0

    • Lombok 1.18.42 1.18.46

    • Maven compiler plugin 3.14.1 3.15.0

    • Maven surefire plugin 3.5.4 3.5.6

    • Maven resources plugin 3.3.1 3.5.0

    • License maven plugin 2.7.0 2.7.1

    • Jacoco Maven Plugin 0.8.14 0.8.15

    • Maven source plugin 3.3.1 3.4.0

    • Central publishing maven plugin 0.9.0 0.11.0

1.4.1

Nov 23, 2025

Changes in testing-utils-mockwebserver module

  • Added possibility to specify Content-Type header in @EnqueueResponse

1.4.0

Nov 22, 2025

New module: testing-utils-mockwebserver

  • Integrates with testing utils and okhttp3 mockwebserver to provide convenience methods & annotations

Changes in testing-utils module

  • Breaking change in TestingUtils interface: removed suite parameters from most methods - it was mostly an unnecessary boilerplate from the user's perspective. Now, these aligned methods implicitly use getSuite() to get the suite, instead of the user having to call it.

  • Deprecated ArtifactType enum, to be replaced in favor of plain strings supported by default constants in DefaultArtifactType

  • Deprecated methods in TestingUtils interface that use ArtifactType enum, added alternatives with string parameters

  • Updated javadocs to reflect the above changes

  • Reorganized method order in TestingUtils interface

  • Bumped dependencies

    • Spring boot dependencies 3.5.6 3.5.8

    • Jacoco maven plugin 0.8.13 0.8.14

1.3.2

Sep 25, 2025

  • Added missing license header in TestApp.java

  • Bumped dependencies

    • Spring boot dependencies 3.5.0 3.5.6

    • Lombok 1.18.38 1.18.42

    • Maven compiler plugin 3.14.0 3.14.1

    • Maven surefire plugin 3.5.3 3.5.4

    • License maven plugin 2.5.0 2.7.0

    • Flatten maven plugin 1.7.0 1.7.3

    • Maven javadoc plugin 3.11.2 3.12.0

    • Maven gpg plugin 3.2.7 3.2.8

    • Central publishing plugin 0.7.0 0.9.0

1.3.1

May 29, 2025

  • Dropped Spring Boot starter parent and switched to manual dependency & plugin management - the former aided by the spring-boot-dependencies bom

  • The value of the @Suite annotation is now required & added runtime check to detect if blank string is used

  • Unit test alignment with dummy Spring Boot test application to allow @WithTestingUtils to import auto configuration - instead of importing manually

  • Extended coverage for @FixedClock

  • Added this website to parent pom url tag

  • The org.eclipse:yasson package is now a test dependency, instead of optional compile - replaced by jakarta.json.bind:jakarta.json.bind-api in the optional compile scope

1.3.0

May 24, 2025

This version is the baseline from which this website tracks changes. As such, no changelog is provided for it.