如何在 JUnit Test Java 中替换 Mockito 以存根类

Posted

技术标签:

【中文标题】如何在 JUnit Test Java 中替换 Mockito 以存根类【英文标题】:How to substitute Mockito in place to stub a class in JUnit Test Java 【发布时间】:2018-06-22 05:10:01 【问题描述】:

我试图在 JUnit 的测试会话期间替换 Mockito 的正确使用来代替存根类。 不幸的是,网上有很多关于 Mockito 的教程,但存根方法的教程较少,我想学习这种技术。

此测试由 Mockito 进行:

 @Test
public void addWrongNewUserSpaceInUsername() throws Exception 

    when(userValidator.isValidUsername(user.getUsername())).thenReturn(false);

    try 
        mockMvc.perform(
                post("/register")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(user)
                        ));
     catch (Exception e) 
        Assert.assertTrue(e.getCause() instanceof UsernameNotValidException);
    

为了澄清这些是所涉及的类:

1) 控制器

   @RestController
public class UserController 

    @Autowired
    RepositoryUserDB repositoryUserDB;

    @Autowired
    UserValidator userValidator;

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public User createUser(@RequestBody User user) 


        if (userValidator.isValidUsername(user.getUsername())) 

            if(!userValidator.isValidPassword(user.getPassword()))
                throw new PasswordNotValidException();
            

            if(userValidator.isValidDateOfBirth(user.getDateOfBirth()) == false)
                throw new DOBNotValidException();
            

            // se lo user e' gia' presente
            if (repositoryUserDB.getUserByUsername(user.getUsername()) == null) 
                return repositoryUserDB.createUser(user);
            
            throw new UsernameAlreadyExistException();
         else throw new UsernameNotValidException();

    

2)repo接口:

public interface RepositoryUserDB 

    User getUserByUsername(String username);

    User createUser(User user);

3)回购:

@Component

    public class MemoryUserDB implements RepositoryUserDB

        Map<String, User> repo;

        public MemoryUserDB() 
            this.repo = new HashMap<>();
        

        @Override
        public  User getUserByUsername(String username) 
            return repo.get(username);
        

        @Override
        public User createUser(User user) 
            repo.put(user.getUsername(),user);
            return repo.get(user.getUsername());
        
    

4) 验证器:

   @Component
public class UserValidator 

    public boolean isValidUsername(String username) 
        return username.matches("^[a-zA-Z0-9]+$");
    

    public boolean isValidPassword(String pwd) 
        if (pwd == null)
            return false;
        return pwd.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)4,.+$");
    

    public boolean isValidDateOfBirth(String DOB) 
        return DOB.matches("^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.)31)\\1|(?:(?:0?[1,3-9]|1[0-2])(\\/|-|\\.)(?:29|30)\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d2)$|^(?:0?2(\\/|-|\\.)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\\/|-|\\.)(?:0?[1-9]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d2)$");
    

5)ResEntityExceptionHandler

    @ControllerAdvice
public class RestEntityExceptionHandler 

    @ExceptionHandler(UsernameNotValidException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "username wrong")
    public void handleUsernameException() 
    

    @ExceptionHandler(UsernameAlreadyExistException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "the username is already presents")
    public void handleUsername() 
    

    @ExceptionHandler(PasswordNotValidException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "password wrong")
    public void handlePasswordException() 
    

    @ExceptionHandler(DOBNotValidException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Date Of Birth wrong")
    public void handleDOBException()
    


【问题讨论】:

如何将验证注入控制器? @MaciejKowalski:通过使用 ControllerAdvice 的类。你需要吗? 查看完整的测试设置会很有用 好的,我将编辑我的问题并添加课程 @MaciejKowalski:做到了。 【参考方案1】:

理论上,对于您的用例,存根方法应该相当简单。 但是由于您依赖于使用 Spring bean 容器的 Spring Boot 测试,因此设置起来非常困难,因为您应该找到一种将模拟 bean 注入容器中以替换实际 bean 的方法:UserValidator。 Spring Boot 测试中的 Mocks 通常依赖于Spring Boot MockBean。 这不是一个 mockito 模拟,但不是很远。 要了解与 Mockito 模拟的区别,您可以参考 to this question。

使用框架可以提供许多开箱即用的功能,但也会限制自己,因为您想绕过框架功能。


假设您没有使用 Spring Boot 进行集成测试,而是进行了真正的单元测试(因此没有 Spring Boot 容器),可以这样执行。

您无需模拟UserValidator.isValidUsername(),而是定义UserValidator 的自定义实现,该实现在您的测试中按预期对方法返回进行存根。 最后,Mockito 或任何模拟框架为您做了什么。

所以这里是存根类:

public class UserValidatorStub extends UserValidator 

    private String expectedUsername;
    private boolean isValidUsername;

    public UserValidatorStub(String expectedUsername, boolean isValidUsername)
          this.expectedUsername = expectedUsername;
          this.isValidUsername = isValidUsername;
    
    public boolean isValidUsername(String username) 
        if (username.equals(expectedUsername))
           return isValidUsername;
        
        // as fallback, it uses the default implementation but you may return false or null as alternative
       return super.isValidUsername(username);
    


它接受一个构造函数来存储传递给存根方法的预期参数以及要返回的存根结果。

现在,您的测试应该如何编写:

 @Test
public void addWrongNewUserSpaceInUsername() throws Exception 

    // inject the mock in the class under test
    UserController userController = new UserController(new UserValidatorStub(user.getUsername(), false));
    try 
        userController.createUser(user);
     catch (Exception e) 
        Assert.assertTrue(e.getCause() instanceof UsernameNotValidException);
    

请注意,该示例依赖于 UserController 中的构造函数注入来设置依赖关系。

【讨论】:

您的干预非常宝贵。 你的问题真的很有趣。在某些特定情况下,我使用存根代替模拟,因为模拟功能缺少一些东西来实现预期的验收测试。不幸的是,由于框架方法,有时很难设置。所以,我喜欢你的问题! 谢谢@davidxxx,我正在努力对 Java 语言更有信心。我只是一名初级开发人员:) 我必须学会提高效率。

以上是关于如何在 JUnit Test Java 中替换 Mockito 以存根类的主要内容,如果未能解决你的问题,请参考以下文章

如何使用junit4写单元测试用例

在IDEA中Java项目如何创建测试类(Junit测试工具)

java基础第十八篇之单元测试注解和动态代理

junit4.11 怎么使用@test

如何在 JUnit 5 中替换 WireMock @Rule 注释?

如何使用 Scala 进行 instanceof 检查(测试)