How JSON Serialization Can Supercharge Your Java Unit Tests
This article explains how leveraging JSON serialization can dramatically simplify and accelerate Java unit test development by reducing boilerplate code for data mocking and verification, offering practical examples, naming conventions, resource organization, and integration tips for backend projects.
Preface
"If a worker wants to do a good job, he must first sharpen his tools." In July this year the author took over several Java backend projects, quickly increased unit‑test coverage to over 70% by adding test cases, and attributes the speed to JSON serialization.
The author systematically summarizes techniques for using JSON serialization in Java unit tests.
1. Verbose Unit‑Test Code
Verbose Data Mocking Code
Mocking Class Property Values
Map<Long, String> languageMap = new HashMap<>(MapHelper.DEFAULT);
languageMap.put(1L, "Java");
languageMap.put(2L, "C++");
languageMap.put(3L, "Python");
languageMap.put(4L, "JavaScript");
... // dozens of lines
Whitebox.setInternalState(developmentService, "languageMap", languageMap);Mocking Method Parameter Values
List<UserCreateVO> userCreateList = new ArrayList<>();
UserCreateVO userCreate0 = new UserCreateVO();
userCreate0.setName("Changyi");
userCreate0.setTitle("Java Developer");
... // dozens of lines
userCreateList.add(userCreate0);
UserCreateVO userCreate1 = new UserCreateVO();
userCreate1.setName("Tester");
userCreate1.setTitle("Java Tester");
... // dozens of lines
userCreateList.add(userCreate1);
... // dozens of items
userService.batchCreate(userCreateList);Mocking Method Return Values
Long companyId = 1L;
List<UserDO> userList = new ArrayList<>();
UserDO user0 = new UserDO();
user0.setId(1L);
user0.setName("Changyi");
user0.setTitle("Java Developer");
... // dozens of lines
userList.add(user0);
UserDO user1 = new UserDO();
user1.setId(2L);
user1.setName("Tester");
user1.setTitle("Java Tester");
... // dozens of lines
userList.add(user1);
... // dozens of items
Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);Verbose Data Verification Code
Validate Method Return Values
Long companyId = 1L;
List<UserVO> userList = userService.queryByCompanyId(companyId);
UserVO user0 = userList.get(0);
Assert.assertEquals("name mismatch", "Changyi", user0.getName());
Assert.assertEquals("title mismatch", "Java Developer", user0.getTitle());
... // dozens of lines
UserVO user1 = userList.get(1);
Assert.assertEquals("name mismatch", "Tester", user1.getName());
Assert.assertEquals("title mismatch", "Java Tester", user1.getTitle());
... // dozens of linesValidate Method Parameter Values
ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));
Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());
List<UserDO> userCreateList = userCreateListCaptor.getValue();
UserDO userCreate0 = userCreateList.get(0);
Assert.assertEquals("name mismatch", "Changyi", userCreate0.getName());
Assert.assertEquals("title mismatch", "Java Developer", userCreate0.getTitle());
... // dozens of lines
UserDO userCreate1 = userCreateList.get(1);
Assert.assertEquals("name mismatch", "Tester", userCreate1.getName());
Assert.assertEquals("title mismatch", "Java Tester", userCreate1.getTitle());
... // dozens of lines2. Simplify with JSON Serialization
Using JSON serialization, the previous verbose code can be replaced with concise statements.
Simplify Data Mocking Code
Mocking Class Property Values
String text = ResourceHelper.getResourceAsString(getClass(), path + "languageMap.json");
Map<Long, String> languageMap = JSON.parseObject(text, new TypeReference<Map<Long, String>>() {});
Whitebox.setInternalState(mobilePhoneService, "languageMap", languageMap);Example languageMap.json content:
{1:"Java",2:"C++",3:"Python",4:"JavaScript",...}Mocking Method Parameter Values
String text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateList.json");
List<UserCreateVO> userCreateList = JSON.parseArray(text, UserCreateVO.class);
userService.batchCreate(userCreateList);Example userCreateList.json content:
[{"name":"Changyi","title":"Java Developer",...},{"name":"Tester","title":"Java Tester",...},...]Mocking Method Return Values
Long companyId = 1L;
String text = ResourceHelper.getResourceAsString(getClass(), path + "userList.json");
List<UserDO> userList = JSON.parseArray(text, UserDO.class);
Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);Example userList.json content:
[{"id":1,"name":"Changyi","title":"Java Developer",...},{"id":2,"name":"Tester","title":"Java Tester",...},...]Simplify Data Verification Code
Validate Method Return Values
Long companyId = 1L;
List<UserVO> userList = userService.queryByCompanyId(companyId);
String text = ResourceHelper.getResourceAsString(getClass(), path + "userList.json");
Assert.assertEquals("User list mismatch", text, JSON.toJSONString(userList));Validate Method Parameter Values
ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));
Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());
String text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateList.json");
Assert.assertEquals("User creation list mismatch", text, JSON.toJSONString(userCreateListCaptor.getValue()));3. Test Case and Resource Naming
Test Class Naming
Test classes should start with the class under test and end with Test, e.g., UserServiceTest. Place them in src/test/java under the same package.
Test Method Naming
Methods should start with test followed by the method name, e.g., testBatchCreate. For multiple cases, add descriptive suffixes like WithSuccess, WithFailure, or WithException.
Test Resource Directory Naming
Resource directories should start with test and end with the class name, e.g., testUserService. They can be placed either alongside the test class in src/test/java or in src/test/resources. Method‑level resource directories follow the test method name, e.g., testUserService/testBatchCreateWithSuccess.
Test Resource File Naming
Use the variable or parameter name with a .json suffix, e.g., userCreateList.json. Ensure the file name matches the variable used in code.
Git Long‑Path Issue
If resource directory names exceed 50 characters, Git may report "Filename too long". Reduce the length or enable long paths with git config --system core.longpaths true.
4. JSON Resource File Usage Cases
Example Test Case
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Mock private UserDAO userDAO;
@Mock private IdGenerator idGenerator;
@InjectMocks private UserService userService;
private static final String RESOURCE_PATH = "testUserService/";
@Before public void beforeTest() {
Whitebox.setInternalState(userService, "canModify", Boolean.TRUE);
}
@Test public void testCreateUserWithCreate() {
Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());
Long userId = 1L;
Mockito.doReturn(userId).when(idGenerator).next();
String path = RESOURCE_PATH + "testCreateUserWithCreate/";
String text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateVO.json");
UserVO userCreate = JSON.parseObject(text, UserVO.class);
Assert.assertEquals("User ID mismatch", userId, userService.createUser(userCreate));
Mockito.verify(userDAO).getIdByName(userCreate.getName());
Mockito.verify(idGenerator).next();
ArgumentCaptor<UserDO> userCreateCaptor = ArgumentCaptor.forClass(UserDO.class);
Mockito.verify(userDAO).create(userCreateCaptor.capture());
text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateDO.json");
Assert.assertEquals("User creation data mismatch", text, JSON.toJSONString(userCreateCaptor.getValue()));
Mockito.verifyNoMoreInteractions(idGenerator, userDAO);
}
// ... other test methods omitted for brevity
}Resource Directory Illustration
5. Sources of JSON Resource Files
Manually Assembled
[{"name":"Changyi","title":"Java Developer",...},{"name":"Tester","title":"Java Tester",...},...]Generated by Code
public static void main(String[] args) {
List<UserCreateVO> userCreateList = new ArrayList<>();
UserCreateVO userCreate0 = new UserCreateVO();
userCreate0.setName("Changyi");
userCreate0.setTitle("Java Developer");
// ... dozens of lines
userCreateList.add(userCreate0);
UserCreateVO userCreate1 = new UserCreateVO();
userCreate1.setName("Tester");
userCreate1.setTitle("Java Tester");
// ... dozens of lines
userCreateList.add(userCreate1);
System.out.println(JSON.toJSONString(userCreateList));
}Extracted from Logs
2021-08-31 18:55:40,867 INFO [UserService.java:34] - Query result for companyId(1): [{"id":1,"name":"Changyi","title":"Java Developer",...},{"id":2,"name":"Tester","title":"Java Tester",...},...]Generated by Integration Tests
@RunWith(PandoraBootRunner.class)
public class UserDaoTest {
@Resource private UserDAO userDAO;
@Test public void testQueryByCompanyId() {
Long companyId = 1L;
List<UserDO> userList = userDAO.queryByCompanyId(companyId);
log.info("userList={}", JSON.toJSONString(userList));
}
}6. JSON Serialization Techniques (Fastjson)
Serialize Objects, Arrays, Collections, Maps
String text = JSON.toJSONString(user);
String text = JSON.toJSONString(userArray);
String text = JSON.toJSONString(userList);
String text = JSON.toJSONString(userMap, SerializerFeature.MapSortField);Serialize Selected Fields
SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
filter.getIncludes().addAll(Arrays.asList("id", "name"));
String text = JSON.toJSONString(user, filter);Exclude Fields
SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
filter.getExcludes().addAll(Arrays.asList("gmtCreate", "gmtModified"));
String text = JSON.toJSONString(user, filter);Custom Serializers
SerializeConfig.getGlobalInstance().put(Geometry.class, new GeometrySerializer());
String text = JSON.toJSONString(geometry);7. JSON Deserialization Techniques (Fastjson)
Deserialize Objects, Arrays, Collections, Maps
UserVO user = JSON.parseObject(text, UserVO.class);
UserVO[] users = JSON.parseObject(text, UserVO[].class);
List<UserVO> userList = JSON.parseArray(text, UserVO.class);
Map<Long, UserVO> userMap = JSON.parseObject(text, new TypeReference<Map<Long, UserVO>>() {});Deserialize with Non‑Public Fields
UserVO user = JSON.parseObject(text, UserVO.class, Feature.SupportNonPublicField);Deserialize Builder‑Pattern Classes
User user = JSON.parseObject(text, User.UserBuilder.class, Feature.SupportNonPublicField).build();Custom Deserializers
ParserConfig.getGlobalInstance().putDeserializer(Geometry.class, new GeometryDeserializer());
Geometry geometry = JSON.parseObject(text, Geometry.class);8. When JSON Serialization Is Unnecessary
For simple pass‑through parameters or return values, or when only a few fields of an object are needed, adding JSON conversion adds overhead without benefit.
9. Advanced Mockito Uses with JSON
Mock Multiple Return Values
String text = ResourceHelper.getResourceAsString(getClass(), path + "recordList.json");
Record[] records = JSON.parseObject(text, Record[].class);
Mockito.doReturn(records[0], ArrayUtils.subarray(records, 1, records.length)).when(recordReader).read();Mock Return Values Based on Arguments
String text = ResourceHelper.getResourceAsString(getClass(), path + "roleMap.json");
Map<Long, String> roleIdMap = JSON.parseObject(text, new TypeReference<Map<Long, String>>() {});
Mockito.doAnswer(invocation -> roleIdMap.get(invocation.getArgument(0))).when(roleService).get(Mockito.anyLong());Verify Multiple Calls with JSON Comparison
ArgumentCaptor<UserCreateVO> captor = ArgumentCaptor.forClass(UserCreateVO.class);
Mockito.verify(userDAO, Mockito.atLeastOnce()).create(captor.capture());
String expected = ResourceHelper.getResourceAsString(getClass(), path + "userCreateList.json");
Assert.assertEquals("User creation list mismatch", expected, JSON.toJSONString(captor.getAllValues()));Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
