Tests in Spring Boot #1 — Unit Tests

Ahmet Tuten
TOM Tech
Published in
3 min readMar 26, 2024

Testing is an integral part of the software development lifecycle, ensuring that our applications perform as expected and meet the required quality standards. In this series, we’ll talk about testing in Spring applications.

What we’ll be talking about:

  • Unit tests
  • Integration tests
  • End-to-end tests
  • and I’ll update here as more items are added.

So, what is the purpose of unit tests, and how are we doing it?

Unit tests are all about isolation. We’re trying to see whether the code we wrote have the desired quality in an isolated environment. What do we mean by isolation here?

In unit tests, we’re not actually running the code. By using Mockito, we’re mocking every object possible and we’re explicitly telling the compiler how the object will behave in any related behaviour.

We will be using Mockito + JUnit. Mockito for mocking the objects, JUnit for our assertions.

Let’s start with an example. Say we have a BatchService that does payments in batch, in doPayment function:

class BatchService implements Service {

private PaymentService paymentService;
private CustomerService customerService;

public BatchServiceOutput doPayment(Long id) {
// something something...
Customer customer = customerService.getCustomerById(id);
PaymentInfo paymentInfo = paymentService.getPayment(customer);
// something something...
}

}

As you see, we have 2 more service that are bound to our BatchService. Now, say that our Customer and PaymentInfo classes are:

class Customer {
String name;
Long id;
}

class PaymentInfo {
Customer customer;
int price;
}

To write an unit for these classes, we must provide an isolated environment with mocks, so that we’ll see whether our code flow is working in the way we wanted it to. We can do it like this:

@ExtendWith(MockitoExtension.class)
class BatchServiceTest {

@Mock
private PaymentService paymentService;
@Mock
private CustomerService customerService;
@InjectMocks
private BatchService batchService;

@Test
void doPayment() {
Customer customer = Mockito.mock(Customer.class);
PaymentInfo paymentInfo = Mockito.mock(PaymentInfo.class);

Mockito.when(customerService.getCustomerById(ArgumentMatchers.any()))
.thenReturn(customer);
Mockito.when(paymentService.getPayment(customer)).thenReturn(paymentInfo);

BatchServiceOutput result = batchService.doPayment(2L);

Assertions.assertNotNull(result);
}
}

Let’s examine it item by item:

  • @ExtendWith — adds the core features of JUnit5 to the class in which it is used: runner, error handling, callbacks, etc.
  • @Mock — creates mock objects for relevant classes. We use this annotation for the classes that will be injected to the actual class that we are testing.
  • @InjectMocks — used in the actual test class. If you remember, BatchService has dependencies on CustomerService and PaymentServices. We mocked these two services, and created the mock for BatchService with injecting dependency mocks in it.
  • @Test — annotation for JUnit to search and find the tests.
  • Mockito.mock() — this method does the same work with @Mock. Both can be used, may be preferred over each other for readibility.
  • Mockito.when() / .thenReturn() — this is the part where we explicitly tell the compiler what our mocks’ behaviours are - when “this” happens, return “that”.
  • ArgumentMatchers.any() — when telling behaviours to compiler, we can expand the cases by using any() for more flexibility. If we would like to write more strict tests, instead of using any we use our variables like in our second when().
  • Assertions.assertNotNull()- we are checking whether our variable is not null by using JUnit’s Assertions class. We can check that a variable is true/false, nul/not null, a function throws an exception, etc.

What else could we assert? We can use assertTrueor assertFalse to check booleans. Use assertAll to check whether function runs without any code errors, assertThrows to assert that function throws an exception in the case that we wrote in our unit test, etc. We will examine their uses later.

We can also mock static methods, make functions throw exceptions, use mocks in reflection invoked private methods (even if we shouldn’t) and more. We’ll talk about all the capabilities of Mockito. See you in next one.

--

--