Boost Java Unit Testing with JUnit5, Mockito, and Auto‑Generated Code

This article examines common unit‑testing problems in Java projects, analyzes their root causes such as development and maintenance costs, and presents practical solutions including choosing JUnit5, leveraging Mockito, using in‑memory databases, and employing a custom IntelliJ plugin to automatically generate test code and test data.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Boost Java Unit Testing with JUnit5, Mockito, and Auto‑Generated Code

Introduction

Unit testing is familiar to developers, yet it is often ignored, leading to numerous bugs during the testing phase. Writing tests merely for the sake of having them adds little value, while well‑designed tests bring objective benefits. The article asks how to write good unit tests and what drives developers to do so.

Our Current Situation

Many projects have no unit tests at all.

Developers lack the habit of writing tests or have no time due to tight business schedules.

Tests are written as integration tests (e.g., involving containers or databases), resulting in long execution times and loss of purpose.

There is an over‑reliance on integration testing.

These issues were observed in two projects at AONE, where releases proceeded without proper unit testing.

Root Causes

Development cost – new features require time, and large legacy systems are hard to tackle.

Maintenance cost – any change or refactor forces updates to corresponding unit tests.

ROI – uncertain whether the investment in testing yields positive returns, leading to weak motivation.

How to Solve

To reduce costs, we must choose the right testing framework and tools.

Traditional Unit Test Structure

Test data (inputs and dependencies)

Test method

Return value assertions

@Test
public void testAddGroup() {
    // data
    BuyerGroupDTO groupDTO = new BuyerGroupDTO();
    groupDTO.setGmtCreate(new Date());
    groupDTO.setGmtModified(new Date());
    groupDTO.setName("China");
    groupDTO.setCustomerId(customerId);
    // method
    Result<Long> result = customerBuyerDomainService.addBuyerGroup(groupDTO);
    // assertions
    Assert.assertTrue(result.isSuccess());
    Assert.assertNotNull(result.getData());
}

Simple tests are fine, but complex logic and data make test writing painful, prompting the need for better tools.

Choosing a Test Framework

Switching from JUnit4 to JUnit5 brings parameterized tests and flexible configuration.

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
    assertTrue(StringUtils.isPalindrome(candidate));
}

JUnit5 also supports extensions such as JSON‑file sources:

@ParameterizedTest
@JsonFileSource(resources = { "/com/cq/common/KMPAlgorithm/test.json" })
public void test2Test(JSONObject arg) {
    Animal animal = JSONObject.parseObject(arg.getString("Animal"), Animal.class);
    List<String> stringList = JSONObject.parseArray(arg.getString("List<String>"), String.class);
    when(testService.testOther(any(Student.class))).thenReturn(stringArg);
    when(testService.testMuti(any(List.class), any(Integer.class))).thenReturn(stringList);
    when(testService.getAnimal(any(Integer.class))).thenReturn(animal);
    String result = kMPAlgorithm.test2();
    // todo verify the result
}

Mock Frameworks

Mockito – elegant syntax, good for mocking containers; static method mocking supported from 3.4.x.

EasyMock – similar usage but stricter.

PowerMock – complements Mockito for static methods (does not support JUnit5).

Spock – Groovy‑based testing framework.

Database Layer

H2 in‑memory database can simulate relational databases, providing isolation and automatic cleanup. It is suitable for lightweight integration tests rather than full‑scale unit tests.

JUnit5 and Mockito Details

Dependencies needed:

<!-- junit5 -->
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.7.2</version>
  <scope>test</scope>
</dependency>

<!-- mockito -->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.9.0</version>
  <scope>test</scope>
</dependency>

<!-- mockito‑junit‑jupiter -->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>3.9.0</version>
  <scope>test</scope>
</dependency>

Common annotations:

@InjectMocks – injects mocks into the class under test.

@Mock – creates a mock for a dependency.

@Spy – uses real methods while allowing selective stubbing.

@ExtendWith(MockitoExtension.class)
public class ATest {
    @InjectMocks
    private A a = new A();
    @Mock
    private B b;
    @Spy
    private D d;
    @InjectMocks
    private C c = Mockito.spy(new C());

    @BeforeEach
    public void setUp() throws Exception {
        MockitoAnnotations.openMocks(this);
    }

    @ParameterizedTest
    @ValueSource(strings = {"/com/alibaba/cq/springtest/jcode5test/needMockService/A/func.json"})
    public void funcTest(String str) {
        JSONObject arg = TestUtils.getTestArg(str);
        a.func();
        // todo verify the result
    }
}

Common issues:

Mocking static methods – supported from Mockito 3.4+, otherwise use PowerMock.

Version compatibility – older Mockito versions work with Java 8; newer versions require Java 11 or additional Byte‑Buddy dependency.

JUnit5 Jupiter API mismatches – ensure consistent versions of api, engine, and params (5.7.0+).

Test Code Auto‑Generation

Existing plugins were evaluated:

TestMe – generates basic test code but omits data and advanced JUnit5 features.

JunitGenerate – only scaffolds empty test methods.

Squaretest – rich generation, supports multiple branches, but is commercial and not open‑source.

Since none fit perfectly, a custom IntelliJ plugin named JCode5 was created.

Plugin Installation

Search “JCode5” in the IntelliJ plugin marketplace and install.

Plugin installation screenshot
Plugin installation screenshot

Plugin Usage

The plugin offers three functions:

Generate unit‑test classes.

Generate JSON test data for parameterized tests.

Add test methods when new business logic appears.

Place the cursor on the class to test, invoke the generator via shortcut or menu, and select JCode5.

Generator UI
Generator UI

Generated Example

@ParameterizedTest
@ValueSource(strings = {"/com/cq/common/JCode5/testExtend.json"})
public void testExtendTest(String str) {
    JSONObject arg = TestUtils.getTestArg(str);
    Integer i = arg.getInteger("Integer");
    List<String> stringList = JSONObject.parseArray(arg.getString("List<String>"), String.class);
    String stringArg = arg.getString("String");
    when(testService.testBase(any(Integer.class))).thenReturn(stringArg);
    when(testService.testMuti(any(List.class), any(Integer.class))).thenReturn(stringList);
    when(testService.getStr(any(Integer.class))).thenReturn(stringArg);
    when(testService.testOther(any(Student.class))).thenReturn(stringArg);
    jCode5.testExtend(i);
    // todo verify the result
}

The corresponding JSON file (testExtend.json) contains all required input data, achieving “data and code separation”.

{
  "Integer": 1,
  "String": "test",
  "List<String>": ["test"]
}

Important Notes

The generated code depends on JUnit5 and Mockito; ensure the proper dependencies are added.

Assertions are not auto‑generated; developers must add appropriate assert statements.

Leverage parameterized tests by duplicating and modifying the generated JSON to cover multiple scenarios.

Implementation Overview

JCode5’s core logic mirrors Dubbo’s SPI mechanism: it uses reflection to analyze code paths and then generates corresponding test code.

Implementation diagram
Implementation diagram

Future Plans

Customizable mock data: fixed values, template‑based key‑value files, or Faker‑generated realistic data.

Automatic branch testing for conditional logic.

Other enhancements.

Conclusion

Automatic test generation can significantly reduce development effort and improve test coverage. In our project, the approach added 135 test cases (70% pure unit tests) and increased execution speed compared to integration tests, achieving a respectable coverage rate.

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.

JavaCode Generationtest automationunit testingMockitoJUnit5
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.