【问题标题】:How to inject data-model mock into spring controller?如何将数据模型模拟注入弹簧控制器?
【发布时间】:2019-10-17 05:40:16
【问题描述】:

在我的单元测试中,我需要在 spring @Controller 中模拟一个数据模型实例,否则 @RequestMapping 方法的返回值会出错。

为此,我尝试了以下方法:

  1. 创建一个User mock,调用user.login(),需要返回“true”
  2. 在 LoginController 中注入模拟对象
  3. 存根登录方法以返回 true
  4. 使用 Spring 测试中的 MockMVC 执行 POST /Login
  5. 验证是否调用了 mockUser.login

这里是控制器方法:

@RequestMapping(value = "/Login", method = RequestMethod.POST)
  public String updateUI(Locale locale, Model model, @RequestParam("username") String username,
      @RequestParam("hashedPW") String hashedPW, HttpServletRequest request) {
    model.addAttribute("username", username);
    user = new User(username, username, hashedPW.getBytes(), LoginHandler.getInstance());

    boolean loginResult = user.login();
    if(loginResult == true) {
      return "profile";
    }
    String output = "Failed login (" + username + ") requested, locale = " + locale;
    log(output);
    return "home";
  }

以及我通过注入初始化模拟对象:

@Mock
  private User mockUser;

  @InjectMocks
  private LoginController injectedLoginController;


  @Before
  public void setup() throws ServletException {
    MockitoAnnotations.initMocks(this);
    mvc = MockMvcBuilders.standaloneSetup(injectedLoginController).build();
    LOGFILE = new File("logs/general.log");
  }

最后是单元测试:

@Test
  public void testLoginSuccess() throws Exception {
    String username = "Stefan";
    byte[] hashedPW = "".getBytes();
    when(mockUser.login()).thenReturn(true);
    ResultActions ra = mvc
        .perform(post("/Login").param("username", username).param("hashedPW", hashedPW.toString()))
        .andExpect(status().isOk());

    verify(mockUser).login();
  }

总的来说,我希望控制器处理的 User 对象是 mockUser 类型而不是 User,并且 login() 方法会被调用一次(相应地)并返回“true”。

但我得到的只是 “需要但未调用:mockUser.login() 实际上,与此模拟的交互为零。”

感谢任何解决我的问题的建议,因为我已经为此工作了很长一段时间,但我似乎不明白这一点。

【问题讨论】:

  • 如果你删除最后一行 verify(mockUser).login(); ,你通过了这个测试吗?
  • 没有调用模拟的原因是代码中的user = new User(username, username, hashedPW.getBytes(), LoginHandler.getInstance()); 行。如果您看到那里,则实例化了一个新对象。因此永远不会调用来自模拟对象的方法。
  • 您还没有向我们展示您将模拟用户注入控制器的位置。即使你这样做了,你也会用一个新的用户实例覆盖它
  • 感谢您的帮助! @yuanqingfei:是的,没有这条线就过去了。
  • @Lesiak :如果我没看错的话,我认为注释 Mock 和 InjectMocks 会进行注入,你能确认一下吗?

标签: java spring mockito spring-test


【解决方案1】:

1) 这似乎是一个 IT 测试.. 你不应该强制任何注入.. spring 会这样做:

@InjectMocks
private LoginController injectedLoginController;

2) 你需要监视你的控制器:

@SpyBean
private LoginController injectedLoginController;

3)您需要将User的创建移动到控制器内部的包级方法中:

user = createUser(username, username, hashedPW.getBytes(), LoginHandler.getInstance()); 


...

User createUser(...){
   return new User(username, username, hashedPW.getBytes(), LoginHandler.getInstance());
}

4) 使该方法返回一个模拟用户:

  doReturn(mockUser).when(injectedLoginController).createUser(...);
  when(mockUser.login()).thenReturn(true);

【讨论】:

  • 会调用 mock 吗?因为在 SUT 中,User 对象被实例化为一个 new User(..
  • 更新了我的答案
  • 感谢您的帮助,您的帮助让我想到了我目前正在运行的解决方案。我创建了一个静态 UserFactory,因此我能够注入模拟的 User 和 UserFactory 并修改它们的方法结果。我读过关于间谍的文章,但我尽量避免他们。
  • 好人,他们有自己的位置,但在遗留代码中你不能改变太多
【解决方案2】:

如果您不想更改代码,可以从 PowerMockito 获得帮助,它可以帮助模拟新对象的构造。

@RunWith(PowerMockRunner.class)
@PrepareForTest({User.class})
public class ControllerUnderTest {
  // here User is class for which we want to mock object creation
}

现在,让我们模拟对象

@Test
public void testLoginSuccess() throws Exception {
 ....
 User userMock = PowerMockito.createMock(User.class);

 PowerMockito.whenNew(User.class).withArguments(username, username, hashedPW.getBytes(), LoginHandler.getInstance()).thenReturn(userMock);

 expect(userMock.login()).andReturn(true);

 verify(mockUser).login();
 ...
}

有关powermock的更多详细信息,请查看PowerMockito

【讨论】:

  • 感谢您的建议,我现在花了一些时间阅读有关 PowerMockito 的信息。我相信这会奏效,但这只会弥补我这边糟糕的设计。我现在正在运行另一个解决方案。
猜你喜欢
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-15
  • 2020-11-21
相关资源
最近更新 更多