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

如何用Mockito正确Mock Spring Boot Repository?测试失败求助

问题描述

我是一名刚接触后端领域的前端工程师(FEE),正在用Spring Boot做练习项目。开发了一个AdminAuthService,通过调用AdminRepository存储的Admin对象用户名和密码,验证请求的账号密码是否合法。该服务用Postman测试正常,但JUnit/Mockito单元测试始终无法通过。首次使用JUnit/Mockito,推测是Repository的Mock方式有误。

在Service的validateIsAdmin方法中添加日志后发现:测试时Service使用的是Repository的真实用户名和密码,而非Mock的值,导致测试断言失败。目标是让Service使用Mock的账号密码进行校验。

Service日志代码

log.info("username and password for repository is: " + adminRepository.getAdminUserName() + " | " + adminRepository.getAdminPassword()); 
log.info("Recieved admin auth request with {}", adminRequest.getUsername() + " | " + adminRequest.getPassword());

Service类代码

package com.couvq.readinglist.service;

import com.couvq.readinglist.dto.AdminRequest;
import com.couvq.readinglist.dto.AdminResponse;
import com.couvq.readinglist.repository.AdminRepository;
import lombok.RequiredArgsConstructor;
import lombok.log4j.Log4j2;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Log4j2
public class AdminAuthService {

    private final AdminRepository adminRepository;

    public AdminResponse validateIsAdmin(AdminRequest adminRequest) {
        log.info("username and password for repository is: " + adminRepository.getAdminUserName() + " | " + adminRepository.getAdminPassword());
        log.info("Recieved admin auth request with {}", adminRequest.getUsername() + " | " + adminRequest.getPassword());
        // if username and password of request matches that of repository, isAdmin is true
        if (adminRequest.getUsername().equals(adminRepository.getAdminUserName())
            && adminRequest.getPassword().equals(adminRepository.getAdminPassword())) {
            return AdminResponse.builder()
                    .isAdmin(true)
                    .build();
        } else {
            return AdminResponse.builder()
                    .isAdmin(false)
                    .build();
        }
    }
}

测试用例代码

import com.couvq.readinglist.dto.AdminRequest;
import com.couvq.readinglist.dto.AdminResponse;
import com.couvq.readinglist.repository.AdminRepository;
import com.couvq.readinglist.service.AdminAuthService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@SpringBootTest
@ExtendWith(MockitoExtension.class)
class ReadingListApplicationTests {

    @Autowired
    private AdminAuthService adminAuthService;

    @Mock
    private AdminRepository adminRepository;

    @Test
    public void validateIsAdminWithAdminUserNameAndPasswordReturnsTrueResponse() {
        when(adminRepository.getAdminUserName()).thenReturn("username");
        when(adminRepository.getAdminPassword()).thenReturn("password");

        AdminRequest request = AdminRequest.builder()
                .username("username")
                .password("password")
                .build();

        AdminResponse response = adminAuthService.validateIsAdmin(request);

        AdminResponse expectedResponse = AdminResponse.builder()
                .isAdmin(true)
                .build();

        assertEquals(expectedResponse, response);
    }
}

测试断言失败输出

org.opentest4j.AssertionFailedError: 
Expected :AdminResponse(isAdmin=true)
Actual   :AdminResponse(isAdmin=false)

解决方案

错误根源

你当前的测试用了@SpringBootTest,这会启动完整的Spring应用上下文,@Autowired注入的AdminAuthService是容器内的真实实例,它依赖的AdminRepository也是真实Bean。而你用@Mock创建的AdminRepository是孤立的Mock对象,并没有替换掉容器中的真实实例,所以Service调用的还是真实Repository的方法,导致断言失败。

修复方案(两种可选)

方案1:使用@MockBean替换Spring容器中的Repository(集成测试场景)

把@Mock改成@MockBean,它会自动替换Spring上下文中对应的AdminRepository Bean,这样@Autowired的AdminAuthService就会使用Mock的Repository了。

修改后的测试代码:

import com.couvq.readinglist.dto.AdminRequest;
import com.couvq.readinglist.dto.AdminResponse;
import com.couvq.readinglist.repository.AdminRepository;
import com.couvq.readinglist.service.AdminAuthService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@SpringBootTest
class ReadingListApplicationTests {

    @Autowired
    private AdminAuthService adminAuthService;

    @MockBean // 替换成@MockBean
    private AdminRepository adminRepository;

    @Test
    public void validateIsAdminWithAdminUserNameAndPasswordReturnsTrueResponse() {
        when(adminRepository.getAdminUserName()).thenReturn("username");
        when(adminRepository.getAdminPassword()).thenReturn("password");

        AdminRequest request = AdminRequest.builder()
                .username("username")
                .password("password")
                .build();

        AdminResponse response = adminAuthService.validateIsAdmin(request);

        AdminResponse expectedResponse = AdminResponse.builder()
                .isAdmin(true)
                .build();

        assertEquals(expectedResponse, response);
    }
}

方案2:纯单元测试(不启动Spring上下文)

去掉@SpringBootTest和@Autowired,手动创建AdminAuthService实例并注入Mock的AdminRepository,这种方式更轻量,适合纯单元测试。

修改后的测试代码:

import com.couvq.readinglist.dto.AdminRequest;
import com.couvq.readinglist.dto.AdminResponse;
import com.couvq.readinglist.repository.AdminRepository;
import com.couvq.readinglist.service.AdminAuthService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class) // 仅启动Mockito
class AdminAuthServiceTest { // 建议将测试类名改为对应Service的测试类,更规范

    @Mock
    private AdminRepository adminRepository;

    // 手动创建Service实例,注入Mock的Repository
    private AdminAuthService adminAuthService = new AdminAuthService(adminRepository);

    @Test
    public void validateIsAdminWithAdminUserNameAndPasswordReturnsTrueResponse() {
        when(adminRepository.getAdminUserName()).thenReturn("username");
        when(adminRepository.getAdminPassword()).thenReturn("password");

        AdminRequest request = AdminRequest.builder()
                .username("username")
                .password("password")
                .build();

        AdminResponse response = adminAuthService.validateIsAdmin(request);

        AdminResponse expectedResponse = AdminResponse.builder()
                .isAdmin(true)
                .build();

        assertEquals(expectedResponse, response);
    }
}

额外注意

要确保AdminResponse类重写了equals()和hashCode()方法,因为assertEquals是通过这两个方法比较对象是否相等的。如果使用Lombok,可以给AdminResponse添加@EqualsAndHashCode注解,让Builder生成的实例能正确比较。

内容的提问来源于stack exchange,提问作者couvq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:20:34