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

Mockito测试报错:when()需传入mock方法调用参数(BCrypt问题)

问题:模拟BCrypt.checkpw静态方法时抛出MissingMethodInvocationException

编写单元测试尝试模拟BCrypt.checkpw静态方法时,抛出MissingMethodInvocationException,提示when()需传入mock对象的方法调用参数。尝试过直接mock BCrypt对象、使用@MockBean注解,均未解决问题。

相关代码

UsuarioController.java

@GetMapping("lastPassword")
public ResponseEntity<Object> lastPassword(@RequestParam(required = true) String correo, @RequestParam(required = true) String newPassword) { // 检查输入的密码是否为用户最后一次使用的密码
    try {
        Optional<usuarioVista> tmp = repositoryVista.findByCorreo(correo);
        Optional<Usuario> usuario = service.findById(Integer.toUnsignedLong(tmp.get().getIdUsuario())); // 根据收到的邮箱查找用户
        String password = "$2a$14$" + usuario.get().getPassword(); // 获取加密后的密码
        Boolean isLast = BCrypt.checkpw(newPassword, password); // 检查输入的密码是否与加密密码匹配

        return new ResponseEntity<>(isLast, HttpStatus.OK);
    } catch (Exception e) {
        return ResponseEntity.badRequest().build();
    }
}

UsuarioControllerTest.java

@Test
void lastPassword() throws Exception {
    String correo = "example@gmail.com";
    String newPassword = "Password123@";
    usuarioVista usuario = new usuarioVista();
    Usuario usuarioOption = mock(Usuario.class);
    Optional<usuarioVista> usuarioCorreo = Optional.of(usuario);
    Optional<Usuario> usuarioId = Optional.of(usuarioOption);
    BCrypt encriptado = mock(BCrypt.class);
    String password = "$2a$14$9SXpfOnx/waQlmHn6L3yp.L5d7wlf.ZMqVIwaDHuq8KDhppWtHi9K";
    Boolean isLast = true;

    Mockito.when(repositoryVista.findByCorreo(correo)).thenReturn(usuarioCorreo);
    Mockito.when(usuarioService.findById(33L)).thenReturn(usuarioId);
    Mockito.when(usuarioOption.getPassword()).thenReturn(newPassword);
    when(encriptado.checkpw(newPassword, password)).thenReturn(isLast);

    ResponseEntity<Object> response = controller.lastPassword(correo, newPassword);

    assertEquals(HttpStatus.OK, response.getStatusCode());
}

错误堆栈信息

%TESTC  1 v2
%TSTTREE2,mx.com.ficachi.master.login.controllers.UsuarioControllerTest,true,1,false,1,UsuarioControllerTest,,[engine:junit-jupiter]/[class:mx.com.ficachi.master.login.controllers.UsuarioControllerTest]
%TSTTREE3,lastPassword(mx.com.ficachi.master.login.controllers.UsuarioControllerTest),false,1,false,2,lastPassword(),,[engine:junit-jupiter]/[class:mx.com.ficachi.master.login.controllers.UsuarioControllerTest]/[method:lastPassword()]
%TESTS  3,lastPassword(mx.com.ficachi.master.login.controllers.UsuarioControllerTest)

%ERROR  3,lastPassword(mx.com.ficachi.master.login.controllers.UsuarioControllerTest)
%TRACES 
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

        at mx.com.ficachi.master.login.controllers.UsuarioControllerTest.lastPassword(UsuarioControllerTest.java:148)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

%TRACEE 

%TESTE  3,lastPassword(mx.com.ficachi.master.login.controllers.UsuarioControllerTest)

%RUNTIME8003

解决方案

原因分析

原测试代码中mock的是BCrypt类的实例,但Controller中调用的是静态方法BCrypt.checkpw(),两者没有关联,导致mock失效。普通Mockito无法直接mock静态方法,需使用静态mock功能或封装静态调用。


方法1:使用Mockito静态mock(适用于Mockito 3.4+)

通过Mockito.mockStatic()创建BCrypt类的静态mock,限定作用域在try-with-resources块内:

修改后的测试代码:

@Test
void lastPassword() throws Exception {
    String correo = "example@gmail.com";
    String newPassword = "Password123@";
    usuarioVista usuario = new usuarioVista();
    usuario.setIdUsuario(33); // 补充设置id,避免后续转换异常
    Usuario usuarioOption = mock(Usuario.class);
    Optional<usuarioVista> usuarioCorreo = Optional.of(usuario);
    Optional<Usuario> usuarioId = Optional.of(usuarioOption);
    String storedPasswordSuffix = "encryptedPasswordSuffix";
    String fullEncryptedPassword = "$2a$14$" + storedPasswordSuffix;
    Boolean isLast = true;

    Mockito.when(repositoryVista.findByCorreo(correo)).thenReturn(usuarioCorreo);
    Mockito.when(usuarioService.findById(33L)).thenReturn(usuarioId);
    Mockito.when(usuarioOption.getPassword()).thenReturn(storedPasswordSuffix);

    // 静态mock BCrypt类,指定checkpw的行为
    try (MockedStatic<BCrypt> mockedBCrypt = Mockito.mockStatic(BCrypt.class)) {
        mockedBCrypt.when(() -> BCrypt.checkpw(newPassword, fullEncryptedPassword))
                    .thenReturn(isLast);

        ResponseEntity<Object> response = controller.lastPassword(correo, newPassword);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals(isLast, response.getBody());
    }
}

方法2:封装静态调用为服务类(推荐,符合依赖注入原则)

将BCrypt的静态调用封装到一个独立的服务类中,通过依赖注入在Controller中使用,测试时mock该服务类即可。

  1. 创建密码验证服务类:
@Service
public class PasswordValidationService {
    public boolean checkPassword(String rawPassword, String encryptedPassword) {
        return BCrypt.checkpw(rawPassword, encryptedPassword);
    }
}
  1. 修改Controller注入该服务:
@RestController
public class UsuarioController {
    private final PasswordValidationService passwordValidationService;
    private final UsuarioRepositoryVista repositoryVista;
    private final UsuarioService service;

    // 构造函数注入服务
    public UsuarioController(PasswordValidationService passwordValidationService,
                             UsuarioRepositoryVista repositoryVista,
                             UsuarioService service) {
        this.passwordValidationService = passwordValidationService;
        this.repositoryVista = repositoryVista;
        this.service = service;
    }

    @GetMapping("lastPassword")
    public ResponseEntity<Object> lastPassword(@RequestParam(required = true) String correo, @RequestParam(required = true) String newPassword) {
        try {
            Optional<usuarioVista> tmp = repositoryVista.findByCorreo(correo);
            Optional<Usuario> usuario = service.findById(Integer.toUnsignedLong(tmp.get().getIdUsuario()));
            String password = "$2a$14$" + usuario.get().getPassword();
            Boolean isLast = passwordValidationService.checkPassword(newPassword, password); // 使用服务类调用

            return new ResponseEntity<>(isLast, HttpStatus.OK);
        } catch (Exception e) {
            return ResponseEntity.badRequest().build();
        }
    }
}
  1. 修改测试代码mock服务类:
@ExtendWith(MockitoExtension.class)
public class UsuarioControllerTest {
    @InjectMocks
    private UsuarioController controller;
    @Mock
    private UsuarioRepositoryVista repositoryVista;
    @Mock
    private UsuarioService usuarioService;
    @Mock
    private PasswordValidationService passwordValidationService; // mock服务类

    @Test
    void lastPassword() throws Exception {
        String correo = "example@gmail.com";
        String newPassword = "Password123@";
        usuarioVista usuario = new usuarioVista();
        usuario.setIdUsuario(33);
        Usuario usuarioOption = mock(Usuario.class);
        Optional<usuarioVista> usuarioCorreo = Optional.of(usuario);
        Optional<Usuario> usuarioId = Optional.of(usuarioOption);
        String storedPasswordSuffix = "encryptedPasswordSuffix";
        String fullEncryptedPassword = "$2a$14$" + storedPasswordSuffix;
        Boolean isLast = true;

        Mockito.when(repositoryVista.findByCorreo(correo)).thenReturn(usuarioCorreo);
        Mockito.when(usuarioService.findById(33L)).thenReturn(usuarioId);
        Mockito.when(usuarioOption.getPassword()).thenReturn(storedPasswordSuffix);
        Mockito.when(passwordValidationService.checkPassword(newPassword, fullEncryptedPassword))
               .thenReturn(isLast);

        ResponseEntity<Object> response = controller.lastPassword(correo, newPassword);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals(isLast, response.getBody());
    }
}

内容的提问来源于stack exchange,提问作者Axel Fernando Castro Jimenez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 06:12:33