You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Service层JUnit测试Mockito断言失败:结果为null

Service层JUnit测试问题排查与修复

问题现象

  1. 调用bookListService.listBooks(2,1,1L)返回null,触发org.opentest4j.AssertionFailedError(预期值1,实际值null)
  2. 另一种场景下返回的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:31:02