Mastering Java Unit Testing: A Deep Dive into Mockito & PowerMock

This comprehensive guide walks you through writing effective Java unit tests, covering test framework basics, Maven dependencies, typical code examples, step‑by‑step test case creation, mocking techniques with Mockito and PowerMock, verification strategies, handling special cases, and best practices for achieving high coverage.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering Java Unit Testing: A Deep Dive into Mockito & PowerMock

Preface

Inspired by the Qing‑dynasty thinker Zhang Xuecheng, the author emphasizes the importance of deep focus in technology and shares experiences of achieving 100% line coverage in a recent project.

1 Writing Unit Test Cases

1.1 Test Framework Overview

Mockito is a lightweight mocking framework that isolates the class under test from its dependencies. PowerMock extends Mockito to mock static, private, final, and constructor methods, but its use can affect JaCoCo coverage.

The author recommends using Mockito first and only resorting to PowerMock when necessary, avoiding PowerMock features that interfere with coverage.

1.2 Adding Test Framework Dependencies

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-core</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>

PowerMock already bundles Mockito and JUnit, so separate Mockito/JUnit dependencies are unnecessary.

1.3 Typical Code Example

/**
 * User service class
 */
@Service
public class UserService {
    /** User DAO */
    @Autowired
    private UserDAO userDAO;
    /** ID generator */
    @Autowired
    private IdGenerator idGenerator;
    @Value("${userService.canModify}")
    private Boolean canModify;

    /** Create user */
    public Long createUser(UserVO userCreate) {
        // Get user ID
        Long userId = userDAO.getIdByName(userCreate.getName());
        if (Objects.isNull(userId)) {
            userId = idGenerator.next();
            UserDO create = new UserDO();
            create.setId(userId);
            create.setName(userCreate.getName());
            userDAO.create(create);
        } else if (Boolean.TRUE.equals(canModify)) {
            UserDO modify = new UserDO();
            modify.setId(userId);
            modify.setName(userCreate.getName());
            userDAO.modify(modify);
        } else {
            throw new UnsupportedOperationException("不支持修改");
        }
        return userId;
    }
}

1.4 Test Case Implementation

/**
 * User service test class
 */
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @Mock
    private IdGenerator idGenerator;
    @InjectMocks
    private UserService userService;

    @Before
    public void beforeTest() {
        Whitebox.setInternalState(userService, "canModify", Boolean.TRUE);
    }

    @Test
    public void testCreateUserWithNew() {
        Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());
        Long userId = 1L;
        Mockito.doReturn(userId).when(idGenerator).next();
        String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
        UserVO userCreate = JSON.parseObject(text, UserVO.class);
        Assert.assertEquals("用户标识不一致", userId, userService.createUser(userCreate));
        ArgumentCaptor<UserDO> userCreateCaptor = ArgumentCaptor.forClass(UserDO.class);
        Mockito.verify(userDAO).create(userCreateCaptor.capture());
        String expected = ResourceHelper.getResourceAsString(getClass(), "userCreateDO.json");
        Assert.assertEquals("用户创建不一致", expected, JSON.toJSONString(userCreateCaptor.getValue()));
        Mockito.verifyNoMoreInteractions(idGenerator, userDAO);
    }

    @Test
    public void testCreateUserWithOld() {
        Long userId = 1L;
        Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
        String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
        UserVO userCreate = JSON.parseObject(text, UserVO.class);
        Assert.assertEquals("用户标识不一致", userId, userService.createUser(userCreate));
        ArgumentCaptor<UserDO> userModifyCaptor = ArgumentCaptor.forClass(UserDO.class);
        Mockito.verify(userDAO).modify(userModifyCaptor.capture());
        String expected = ResourceHelper.getResourceAsString(getClass(), "userModifyDO.json");
        Assert.assertEquals("用户修改不一致", expected, JSON.toJSONString(userModifyCaptor.getValue()));
        Mockito.verifyNoInteractions(idGenerator);
        Mockito.verifyNoMoreInteractions(userDAO);
    }

    @Test
    public void testCreateUserWithException() {
        Whitebox.setInternalState(userService, "canModify", Boolean.FALSE);
        Long userId = 1L;
        Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
        String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
        UserVO userCreate = JSON.parseObject(text, UserVO.class);
        UnsupportedOperationException exception = Assert.assertThrows(
            "返回异常不一致",
            UnsupportedOperationException.class,
            () -> userService.createUser(userCreate));
        Assert.assertEquals("异常消息不一致", "不支持修改", exception.getMessage());
    }
}

1.5 JSON Test Data

{"name":"test"}
{"id":1,"name":"test"}

2 Test Case Writing Process

The process consists of four stages: defining the object under test, mocking dependencies, invoking the method, and verifying behavior.

2.1 Defining the Object Under Test

Options include direct construction, Mockito.spy, and @Spy annotation.

UserService userService = new UserService();
UserService userService = Mockito.spy(new UserService());
UserService userService = Mockito.spy(UserService.class);

2.2 Mocking Dependency Objects

Use @Mock, Mockito.mock, or PowerMock for static/final/private methods.

@Mock private UserDAO userDAO;
@Mock private IdGenerator idGenerator;

2.3 Injecting Dependencies

Inject via setters, ReflectionTestUtils.setField, Whitebox.setInternalState, or @InjectMocks.

userService.setUserDAO(userDAO);
ReflectionTestUtils.setField(userService, "maxCount", 100);
Whitebox.setInternalState(userService, "maxCount", 100);

2.4 Mocking Dependency Methods

Demonstrates return values, exceptions, argument matchers, and special method mocking (final, private, static, constructors).

// Return value
Mockito.doReturn(user).when(userDAO).get(userId);
// Throw exception
Mockito.doThrow(PersistenceException.class).when(userDAO).get(Mockito.anyLong());
// Mock static method
PowerMockito.mockStatic(HttpHelper.class);
PowerMockito.when(HttpHelper.httpPost(SERVER_URL)).thenReturn(response);

2.5 Invoking the Method Under Test

Shows how to call public, protected, package‑private, and private methods using direct calls or Whitebox.invokeMethod.

userService.deleteUser(userId);
User user = (User)Whitebox.invokeMethod(userService, "isSuper", userId);

2.6 Verifying Dependency Calls

Uses Mockito.verify with argument matchers, call counts, ArgumentCaptor, and special verifications for final, private, static, and constructor methods.

Mockito.verify(userDAO).delete(userId);
Mockito.verify(userDAO, Mockito.times(2)).delete(Mockito.anyLong());
ArgumentCaptor<UserDO> captor = ArgumentCaptor.forClass(UserDO.class);
Mockito.verify(userDAO).create(captor.capture());
PowerMockito.verifyStatic(StringUtils.class);
StringUtils.isEmpty(str);

2.7 Validating Data Objects

Uses JUnit assertions to check nullity, booleans, reference equality, value equality, and complex bean fields.

Assert.assertNull("用户标识必须为空", userId);
Assert.assertTrue("返回值必须为真", NumberHelper.isPositive(1));
Assert.assertEquals("用户名称不一致", "admin", userName);
Assert.assertEquals("用户标识不一致", Long.valueOf(1L), user.getId());

2.8 Exception Verification

Shows three ways: @Test(expected=...), @Rule ExpectedException, and Assert.assertThrows.

@Test(expected = ExampleException.class)
public void testGetUser() { ... }

@Rule public ExpectedException exception = ExpectedException.none();
exception.expect(ExampleException.class);
exception.expectMessage("用户不存在");

ExampleException ex = Assert.assertThrows("异常类型不一致", ExampleException.class, () -> userService.getUser(id));
Assert.assertEquals("异常消息不一致", "处理异常", ex.getMessage());

2.9 Verifying No Interactions

Mockito.verifyNoInteractions(idGenerator, userDAO);
Mockito.verifyNoMoreInteractions(idGenerator, userDAO);
Mockito.clearInvocations(idGenerator, userDAO);

3 Typical Cases and Solutions

3.1 Test Framework Limitations

Lists common pitfalls such as inability of Mockito to mock static/final/private methods, parameter matcher limitations, and the need for @PrepareForTest with PowerMock.

3.2 Capturing Modified Arguments

When a captured list is cleared after verification, the captured reference becomes empty. Solution: capture values inside the mock using doAnswer or store them in a separate collection.

Mockito.doAnswer(invocation -> {
    dataList.addAll(invocation.getArgument(0));
    return true;
}).when(dataStorage).test(Mockito.anyList());

3.3 Mocking Lombok @Slf4j Logger

Static final logger cannot be injected with @InjectMocks. Use a helper to set the static field.

FieldHelper.setStaticFinalField(ExampleService.class, "log", log);

3.4 Pandora Container Compatibility

PowerMock’s custom classloader conflicts with Alibaba’s Pandora container. Replace PowerMockRunner with PandoraBootRunner and initialize Mockito annotations manually.

@RunWith(PandoraBootRunner.class)
@Before
public void beforeTest() {
    MockitoAnnotations.initMocks(this);
}

3.5 Eliminating Unchecked Cast Warnings

Provides nine strategies: using annotations, temporary classes, CastUtils.cast, generic methods, doReturn‑when, Whitebox.invokeMethod, instanceof, Class.cast, and avoiding unnecessary casts.

// Annotation
@Mock private Map<Long, String> resultMap;
@Captor private ArgumentCaptor<Map<String, Object>> parameterMapCaptor;

// Temporary class
private interface DataParser extends Function<Record, Object> {}
Function<Record, Object> dataParser = Mockito.mock(DataParser.class);

// CastUtils
Function<Record, Object> parser = CastUtils.cast(Mockito.mock(Function.class));

// doReturn‑when
List<?> valueList = Mockito.mock(List.class);
Mockito.doReturn(valueList).when(listOperations).range(KEY, start, end);

// Whitebox
Map<Long, Double> scoreMap = Whitebox.invokeMethod(userService, "getScoreMap");

// instanceof
if (obj instanceof JSONArray) {
    JSONArray arr = (JSONArray) obj;
}

// Class.cast
if (Objects.equals(clazz, String.class)) {
    return clazz.cast(text);
}

Conclusion

Effective unit testing in Java requires a solid understanding of mocking frameworks, careful handling of static/final/private members, and disciplined verification. By following the outlined process and applying the presented solutions, developers can achieve high test coverage and maintainable test suites.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaunit testingMockingJUnitPowerMockMockitotest coverage
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.