Testing
Last modified on Fri 17 Apr 2026

When writing tests, AssertJ assertions are used by default. Here is a quick start guide for AssertJ.

Keep tests clean

Test Driven Development Laws

  1. You may not write production code until you have written failing unit test.
  2. You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
  3. You may not write more production code than is sufficient to pass the currently failing test.

Unit testing

Unit tests are used for lowest-level testing. They generally focus on methods and small scope and minimum number of dependencies is what makes them fast. When possible, around 60% of test cases should be unit tests.

Best practices:

// rest of test class removed for brevity } ```

// Do this instead
public final class TestFoo {
    private FooDependency fooDependency;

    @Before
    public void setUp() {
        fooDependency = new FooDependency();
    }
}

Modern Unit Testing Practices

@Mock Seller seller;

@InjectMocks Shop shop;

void shouldBuyBread() throws Exception { // given given(seller.askForBread()).willReturn(new Bread());

// when
Goods goods = shop.buyBread();

// then
assertThat(goods, containBread());

} ```

Integration testing

Integration tests are used to verify interactions between multiple classes. They are slower than unit tests and they make around 30% of total number of tests. Validating responses from APIs or Web applications is the most common example of integration testing. Next sub-chapters will describe use of REST-assured for that purpose.

About REST-assured

A great tool for validating responses received from REST service is REST-assured. Take a look at some some simple functionality-describing examples here. REST-assured has validation support for Json Schema, XSD Schema, DTD and many more.

Modern Integration Testing Practices

Learning tests

Learning tests are used to get familiar with third-party libraries. It is a quick way for a developer to check his understanding of API usage and can be very useful to detect future changes. When those tests start to fail after some time, it means that something has changed in the API, so the developer has to update his code to conform with the changes, or stick with an older version of the API, if possible.

Mocking

Mockito

Mockito is a mocking framework for unit tests in Java. A short intro, some test examples and reference to documentation can be found here, and motivation behind it and it's features here. Mockito is included in Spring Boot as a dependency by default. In case you don't use Spring Boot, here is an example for adding it to project build using Gradle: gradle repositories { jcenter() } dependencies { testCompile "org.mockito:mockito-core:2.+" } Now interaction can be verified: ```java import static org.mockito.Mockito.*;

// mock creation List mockedList = mock(List.class);

// using a mock object - it does not throw any "unexpected interaction" exception mockedList.add("one"); mockedList.clear();

// selective, explicit, highly readable verification verify(mockedList).add("one"); verify(mockedList).clear(); And method calls can be stubbed this way: java // you can mock concrete classes, not only interfaces LinkedList mockedList = mock(LinkedList.class);

// stubbing appears before the actual execution when(mockedList.get(0)).thenReturn("first");

// the following prints "first" System.out.println(mockedList.get(0));

// the following prints "null" because get(999) was not stubbed System.out.println(mockedList.get(999)); ```

Use Mockito annotations: ```java @ExtendWith(MockitoExtension.class) class TestClass { @Mock private UserRepository userRepository;

@InjectMocks
private UserService userService;

} ```

Use argument matchers: java when(userRepository.findById(any())).thenReturn(user);

Use argument captors: ```java @Captor private ArgumentCaptor userCaptor;

@Test void shouldSaveUserWithCorrectData() { // Given User userToSave = new User("John", "john@example.com");

// When
userService.saveUser(userToSave);

// Then
verify(userRepository).save(userCaptor.capture());
User savedUser = userCaptor.getValue();

assertThat(savedUser.getName()).isEqualTo("John");
assertThat(savedUser.getEmail()).isEqualTo("john@example.com");

} ```

Argument captors are particularly useful when: * You need to verify complex objects passed to mocked methods * You want to verify multiple calls to the same method with different arguments * You need to verify the state of objects that were modified by the method under test

Example with multiple captures: ```java @Test void shouldSaveMultipleUsers() { // Given User user1 = new User("John", "john@example.com"); User user2 = new User("Jane", "jane@example.com");

// When
userService.saveUser(user1);
userService.saveUser(user2);

// Then
verify(userRepository, times(2)).save(userCaptor.capture());
List<User> savedUsers = userCaptor.getAllValues();

assertThat(savedUsers).hasSize(2);
assertThat(savedUsers.get(0).getName()).isEqualTo("John");
assertThat(savedUsers.get(1).getName()).isEqualTo("Jane");

} ```

Wiremock

WireMock is a simulator for HTTP-based APIs. Some might consider it a service virtualization tool or a mock server. Wiremock is widely used in unit and integration testing. It can be run from JUnit 4.x Rule, Java and as a Standalone Process. It supports Stubbing, Veryfing, Request Matching, Response Templating etc. Visit the WireMock docs for more information about WireMock.

Localstack

Localstack provides an easy-to-use test/mocking framework for developing cloud applications.

Key Features

Using Localstack with TestContainers

@Testcontainers
class AwsServiceTest {
    @Container
    static LocalStackContainer localstack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:latest"))
            .withServices(LocalStackContainer.Service.S3, LocalStackContainer.Service.SQS);

    private AmazonS3 s3;
    private AmazonSQS sqs;

    @BeforeEach
    void setUp() {
        s3 = AmazonS3ClientBuilder
                .standard()
                .withEndpointConfiguration(localstack.getEndpointConfiguration(LocalStackContainer.Service.S3))
                .withCredentials(localstack.getDefaultCredentialsProvider())
                .build();

        sqs = AmazonSQSClientBuilder
                .standard()
                .withEndpointConfiguration(localstack.getEndpointConfiguration(LocalStackContainer.Service.SQS))
                .withCredentials(localstack.getDefaultCredentialsProvider())
                .build();
    }

    @Test
    void testS3Operations() {
        String bucketName = "test-bucket";
        s3.createBucket(bucketName);

        assertThat(s3.listBuckets())
            .extracting(Bucket::getName)
            .contains(bucketName);
    }
}

Best Practices for Localstack Testing

Resources: - Localstack Documentation - TestContainers Localstack Module - AWS SDK with Localstack