Skip to main content

jUnit Stuff

Mockito Unit Test vs Spring Boot Test

This article organizes two common testing methods:

  1. Pure Unit Testing (Mockito + JUnit)
  2. Spring Boot Testing (@SpringBootTest excluding auto-configuration)

Mockito JUnit

Features:

  • Does not depend on Spring container
  • Uses @ExtendWith(MockitoExtension.class)
  • Suitable for Service layer logic testing
Example Code
CodeEntityServiceImplTest
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class CodeEntityServiceImplTest {

@InjectMocks
private CodeEntityServiceImpl codeEntityService;

@Mock
private CodeEntityMapper codeEntityMapper;

@Test
void testGetByIdMine_success() {
CodeEntityPO po = new CodeEntityPO();
po.setUsername("Candy");
po.setCode("abc");

Mockito.when(codeEntityMapper.selectById("abc")).thenReturn(po);

CodeEntityPO result = codeEntityService.getByIdMine("abc");

assertEquals("Candy", result.getUsername());
assertEquals("abc", result.getCode());
}

@Test
void testGetByIdMine_nullShouldThrow() {
Mockito.when(codeEntityMapper.selectById("abc")).thenReturn(null);

Exception ex = assertThrows(IllegalArgumentException.class, () -> {
codeEntityService.getByIdMine1("abc");
});

assertEquals("Object must not be null", ex.getMessage());
}
}

✅ Pros:

  • Fast execution
  • Does not depend on database or Spring container
  • Logic testing is clear

Spring Boot test

Features:

  • Depends on Spring Boot context, but excludes database etc. auto-configuration
  • Uses @MockBean to replace Mapper or Service
  • Suitable for Service/Controller logic testing
Example Code
CodeEntityServiceImplBootTest
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.beans.factory.annotation.Autowired;

@SpringBootTest(
classes = YourMainApplication.class,
properties = {
"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
}
)
class CodeEntityServiceImplBootTest {

@Autowired
private CodeEntityServiceImpl codeEntityService;

@MockBean
private CodeEntityMapper codeEntityMapper;

@Test
void testGetByIdMine_success() {
CodeEntityPO po = new CodeEntityPO();
po.setUsername("Candy");
po.setCode("abc");

Mockito.when(codeEntityMapper.selectById("abc")).thenReturn(po);

CodeEntityPO result = codeEntityService.getByIdMine("abc");

assertEquals("Candy", result.getUsername());
assertEquals("abc", result.getCode());
}
}

Can also use yml file to exclude auto-configuration:

application-test.yml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
CodeEntityServiceImplBootTest
@SpringBootTest(classes = App.class)
@ActiveProfiles("test")

✅ Pros:

  • Spring context available, dependency injection normal
  • Can test Service with transactions or AOP
  • Avoid database connection errors

Complete Code Record

Static Method Mock

Static Method Mock
StaticMockTest
import com.whalefall541.staticmock.ExternalLib;
import com.whalefall541.staticmock.MyService;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import java.util.function.Supplier;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;

class StaticMockTest {
@Test
void testStaticMockWithSupplier() {
// 1. Create the static mock within a try-with-resources block
try (MockedStatic<ExternalLib> mockedLib = mockStatic(ExternalLib.class)) {
// 2. Define behavior: When compute() is called with ANY Supplier, return "Mocked"
mockedLib.when(() -> ExternalLib.compute(any(Supplier.class)))
.thenReturn("Mocked Result");
// 3. Execute the service method
MyService service = new MyService();
String result = service.getProcessedData();
// 4. Verify the result is what we mocked
assertEquals("Mocked Result", result);
// Optional: Verify the static method was called exactly once
mockedLib.verify(() -> ExternalLib.compute(any(Supplier.class)));
}
}

@Test
void testGetProcessedData1_FailureInsideSupplier() {
try (MockedStatic<ExternalLib> mockedLib = mockStatic(ExternalLib.class)) {
mockedLib.when(() -> ExternalLib.compute(any()))
.thenCallRealMethod();
MyService service = new MyService();
Exception exception = assertThrows(IllegalArgumentException.class, service::getProcessedData1);
assertEquals("This will fail", exception.getMessage());
}
}
}

Assert log content

  1. System out redirection

  2. LogCaptor (Concurrent execution scenario has problems)

MyServiceTest
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
// If CapturedOutput is used by several methods, just opening next line comment
// @SpringBootTest
class MyServiceTest {

@Test
void shouldLogMessage(CapturedOutput output) {
MyService service = new MyService();
service.doSomething();
assertThat(output.getOut()).contains("Expected log message");
}
}

Mock annotation

  1. using spring boot test

getting the real annotation from classes

  1. using a tempt inner class
@UseTemplate(DemoTemplate.class)
static class DemoBusiness implements Business<String> {
@Override
public void process(String input) {
// no-op
}
}

static class DemoTemplate implements Template<String, DemoBusiness> {
@Override
public void handler(String txcode, String param, DemoBusiness businessService) {
// mock behavior
}
}
@Test
void testInit_withUseTemplateAnnotation() {
ApplicationContext ctx = mock(ApplicationContext.class);
DemoTemplate demoTemplate = new DemoTemplate();
when(ctx.getBean(DemoTemplate.class)).thenReturn(demoTemplate);

Map<String, DemoBusiness> map = Map.of("job1", new DemoBusiness());

RegisterEnginV3<String, DemoBusiness> engin =
new RegisterEnginV3<>(map, Map.of(), new HashMap<>(), ctx);

engin.init(); // ✅ Will read @UseTemplate(DemoTemplate.class)
}
  1. new a annotion instance to use.

Influence between unit test methods in the same class

very call count is originally once, previous test method will affect subsequent methods, need to add this on unit test class.

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)

Depends on @PostConstruct

You can create it, pass parameter by new construction, then call the init method .

@Service
@AllArgsConstructor
public class RegisterEnginV3<T, S extends Business<T>> {

@PostConstruct
public void init() {
businessesMap.forEach((txcode, businessService) -> {
// 1. Resolve @UseTemplate annotation
UseTemplate ann = businessService.getClass()
.getAnnotation(UseTemplate.class);

// 2. Get corresponding Template Bean
Template<T, S> templateToUse =
(Template<T, S>) applicationContext
.getBean(ann.value());

// 3. Generic type check
checkConsistentGenericType(businessService, templateToUse);

// 4. Register to execution registry
registry.put(txcode,
param -> templateToUse.handler(txcode, param, businessService));
});
}

public void run(String businessType, T params) {
registry.get(businessType).accept(params);
}
}

@MockBean Failure Problem

debug found that a property inside a @Resource object was already written with @MockBean in unit test but still injected real object

✅ Solution 1: Use instead

@Autowired

Most direct, officially recommended way.

@MockBean is designed specifically for @Autowired mechanism.

Mocked method changes argument properties

Very good question 👏

Your scenario is very typical:

✅ Problem Scenario

An object mocked by @MockBean (such as service or client),

It has a void method, which modifies the object you passed in (e.g. setting fields).

In unit test, you want to mock this behavior, letting the object's properties be set.

@Service
public class OrderService {
@Autowired
private PaymentClient paymentClient;

public void process(OrderContext ctx) {
paymentClient.fillPaymentInfo(ctx); // void method, internally sets ctx fields
}
}

public class PaymentClient {
public void fillPaymentInfo(OrderContext ctx) {
ctx.setPayStatus("SUCCESS");
ctx.setPayAmount(100);
}
}

doAnswer() allows you to define custom behavior when calling void methods.

@SpringBootTest
class OrderServiceTest {

@Autowired
private OrderService orderService;

@MockBean
private PaymentClient paymentClient;

@Test
void testProcess() {
// 1️⃣ Mock PaymentClient behavior for fillPaymentInfo
doAnswer(invocation -> {
OrderContext ctx = invocation.getArgument(0);
ctx.setPayStatus("MOCK_SUCCESS");
ctx.setPayAmount(999);
return null; // Because target method is void
}).when(paymentClient).fillPaymentInfo(any(OrderContext.class));

// 2️⃣ Call actual business
OrderContext context = new OrderContext();
orderService.process(context);

// 3️⃣ Assert mock effect
assertEquals("MOCK_SUCCESS", context.getPayStatus());
assertEquals(999, context.getPayAmount());
}
}
Agreement
The code part of this work is licensed under Apache License 2.0 . You may freely modify and redistribute the code, and use it for commercial purposes, provided that you comply with the license. However, you are required to:
  • Attribution: Retain the original author's signature and code source information in the original and derivative code.
  • Preserve License: Retain the Apache 2.0 license file in the original and derivative code.
The documentation part of this work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . You may freely share, including copying and distributing this work in any medium or format, and freely adapt, remix, transform, and build upon the material. However, you are required to:
  • Attribution: Give appropriate credit, provide a link to the license, and indicate if changes were made.
  • NonCommercial: You may not use the material for commercial purposes. For commercial use, please contact the author.
  • ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.