Spring Boot Service层JUnit测试Mockito断言失败:结果为null
Service层JUnit测试问题排查与修复
问题现象
- 调用
bookListService.listBooks(2,1,1L)返回null,触发org.opentest4j.AssertionFailedError(预期值1,实际值null) - 另一种场景下返回的
BookResponse列表长度为2,但除title外其他字段均为null
核心问题分析
1. Mock方法的参数匹配不匹配
Service层对分页参数可能做了转换(比如将传入的page从1-based转为0-based索引),但测试中StubBookRepository时用了原始参数(page=1),导致Mockito无法匹配到Stub逻辑,返回默认值null。
2. 对象转换逻辑缺失或不完整
- 如果依赖
ModelMapper/MapStruct等转换工具,测试中未Stub转换方法,或Stub时只设置了title字段; - 手动转换逻辑中,仅给
title赋值,其他字段未处理。
3. 依赖服务的Stub缺失
Service层调用CategoryService等依赖服务获取字段(如分类名称)时,未Stub对应方法,导致返回null,进而让BookResponse对应字段为空。
4. Service层分页逻辑异常
比如分页查询结果为空时,Service层直接返回null而非空列表;或分页参数计算错误,导致查询无结果。
修复方案
1. 修正Mock方法的参数匹配
- 确认Service层对分页参数的处理逻辑,比如如果用
PageRequest.of(page-1, size)实现0-based分页,Stub时要匹配转换后的参数:// 模拟分页查询返回结果,匹配转换后的page参数 Page<Book> bookPage = new PageImpl<>(Arrays.asList(book1, book2)); when(bookRepository.findByUserId(eq(1L), any(Pageable.class))).thenReturn(bookPage); - 用
ArgumentCaptor捕获实际调用参数,验证是否和Stub匹配:ArgumentCaptor<Pageable> pageableCaptor = ArgumentCaptor.forClass(Pageable.class); verify(bookRepository).findByUserId(eq(1L), pageableCaptor.capture()); // 打印实际调用的分页参数,排查是否匹配 System.out.println("实际page值: " + pageableCaptor.getValue().getPageNumber());
2. 完善对象转换逻辑的Stub
如果用转换工具,确保Stub时返回完整的BookResponse对象:
@Mock private BookMapper bookMapper; @BeforeEach void setUp() { BookResponse response1 = new BookResponse(); response1.setTitle("Java in Action"); response1.setAuthor("John Doe"); response1.setCategory("Fiction"); BookResponse response2 = new BookResponse(); response2.setTitle("Spring Boot Guide"); response2.setAuthor("Jane Smith"); response2.setCategory("Science"); when(bookMapper.toBookResponse(eq(book1))).thenReturn(response1); when(bookMapper.toBookResponse(eq(book2))).thenReturn(response2); }
如果是手动转换,检查Service层代码是否给所有字段赋值,测试时构建的Book对象是否包含完整字段值。
3. Stub所有依赖服务的必要方法
检查Service层调用的依赖方法,比如CategoryService的分类查询,补充Stub:
when(categoryService.getCategoryNameById(eq(1L))).thenReturn("Fiction"); when(categoryService.getCategoryNameById(eq(2L))).thenReturn("Science");
4. 修正Service层分页逻辑
确保分页查询为空时返回空列表而非null,比如:
// Service层代码示例 public List<BookResponse> listBooks(int size, int page, Long userId) { Page<Book> bookPage = bookRepository.findByUserId(userId, PageRequest.of(page-1, size)); if (bookPage.isEmpty()) { return Collections.emptyList(); // 替换为返回空列表,而非null } // 转换逻辑... }
修正后的测试代码示例
@ExtendWith(MockitoExtension.class) class BookListServiceTest { @InjectMocks private BookListService bookListService; @Mock private BookRepository bookRepository; @Mock private CategoryService categoryService; @Mock private BookMapper bookMapper; private Book book1; private Book book2; @BeforeEach void setUp() { // 构建完整的Book对象 book1 = new Book(); book1.setId(1L); book1.setTitle("Java in Action"); book1.setAuthor("John Doe"); book1.setCategoryId(1L); book1.setUserId(1L); book2 = new Book(); book2.setId(2L); book2.setTitle("Spring Boot Guide"); book2.setAuthor("Jane Smith"); book2.setCategoryId(2L); book2.setUserId(1L); // Stub依赖服务 when(categoryService.getCategoryNameById(eq(1L))).thenReturn("Fiction"); when(categoryService.getCategoryNameById(eq(2L))).thenReturn("Science"); // Stub转换逻辑 BookResponse response1 = new BookResponse(); response1.setTitle(book1.getTitle()); response1.setAuthor(book1.getAuthor()); response1.setCategory("Fiction"); when(bookMapper.toBookResponse(eq(book1))).thenReturn(response1); BookResponse response2 = new BookResponse(); response2.setTitle(book2.getTitle()); response2.setAuthor(book2.getAuthor()); response2.setCategory("Science"); when(bookMapper.toBookResponse(eq(book2))).thenReturn(response2); // Stub分页查询 Page<Book> bookPage = new PageImpl<>(Arrays.asList(book1, book2)); when(bookRepository.findByUserId(eq(1L), any(Pageable.class))).thenReturn(bookPage); } @Test void listBooks_ShouldReturnCompleteBookResponseList() { List<BookResponse> result = bookListService.listBooks(2, 1, 1L); assertNotNull(result); assertEquals(2, result.size()); assertEquals("John Doe", result.get(0).getAuthor()); assertEquals("Fiction", result.get(0).getCategory()); } }
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

