【问题标题】:Creating a new instance of a bean after each unit test在每个单元测试之后创建一个新的 bean 实例
【发布时间】:2012-03-02 17:21:18
【问题描述】:

我是 Spring 框架的新手,对它使用 Spring Context 的依赖注入功能有疑问。

这是我要为其编写集成测试的类:

public class UserService {

private Validator validator;
private UserRepository userRepository;
private Encryptor encryptor;
private MailService mailService;

...

public void registerUser(User user) {
    user.setPassword(encryptor.encrypt(user.getPassword()));

    Errors errors = new BindException(user, "user");
    validator.validate(user, errors);

    if (errors.getErrorCount() == 0) {
        userRepository.addUser(user);
        mailService.sendMail(user.getEmail());
    }
}

在我的测试中(使用 Mockito),我想确保调用了四个项目,因此我创建了如下测试:

public void testRegisterCallsValidateInValidator() {
    userService.registerUser(testUser);
    verify(userService.getValidator(), times(1)).validate(any(User.class), any(Errors.class));
}

但是,所有测试都失败了,说我多次调用了该方法。我唯一的猜测是 UserService bean 在所有测试开始时创建一次,但不会在每次测试后重新加载。

在我的测试配置中,我使用以下 xml 来决定注入哪些 bean:

<bean id="userService" class="be.kdg.coportio.services.UserService">
    <property name="validator" ref="validator"/>
    <property name="userRepository" ref="userRepository"/>
    <property name="encryptor" ref="encryptor"/>
    <property name="mailService" ref="mailService"/>
</bean>

有什么想法吗?

【问题讨论】:

  • 你有多种测试方法,还是只有你粘贴的那个?
  • 我有四种测试方法(我已经粘贴了其中的一种)。我得到三个失败的测试,说我已经分别调用了我要测试的方法 2、3 和 4 次。

标签: java spring unit-testing


【解决方案1】:

您正在重用您的上下文,为了使测试彼此独立,您可能需要在每次测试后刷新您的上下文以重置所有内容。

我假设您使用的是 Junit 4.5+。它与其他测试框架类似。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"mycontext.xml"})
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyTestClass {
...
    // my tests    
...
}

如果需要“修复”的方法很少,您可以将 @DirtiesContext 放在方法级别,但如果您使用 Spring,最好的选择是在每次测试后进行。

无论如何,我认为您不应该在集成测试中使用模拟/间谍:

  • 在单元测试中,使用模拟(如果需要)并手动注入。在这里,您希望将测试 bean 的行为作为一个单元来验证,因此您可以使用 mock 将其与其余部分隔离开来。这还有一个优势,即 JUnit 通过为每个测试使用不同的测试类实例来隔离您的测试,因此除非您使用 static 或其他对测试不友好的做法,否则一切都会正常工作。

  • 在集成测试中,使用真正的 bean 并让 Spring 注入。这里的目标是验证 bean 与彼此/与环境(数据库、网络等)的交互是否良好。您确实想在此处隔离 bean,因此您不应该使用模拟。

请参阅Spring documentation about testing 了解更多详细说明。

【讨论】:

  • 感谢代码示例。正如我对 Eugen 的回答所评论的那样,我将这两种测试混合在一起。这就是我将在真正的集成测试中使用的内容:)
  • 非常感谢。这也可以应用于扩展 AbstractTestNGSpringContextTests 的 TestNG-Testclasses。
  • AFTER_EACH_TEST_METHOD,太棒了。这确实有助于为我的每个测试创建新的页面对象。
【解决方案2】:

为了明确区分单元测试和集成测试(跳过关于每个类别含义的争论)- 您可以通过两种方式测试您的服务:

  • 通过集成测试 - 您启动整个 Spring 上下文并将服务作为单例 bean 进行测试。
  • 通过单元测试 - 您只需自己初始化服务,模拟需要模拟的内容,无需 Spring。

如果可以的话,我的建议是不要将 Spring 和 mock 混合使用 - 将 Mockito 用于单元测试(从外观上看这是你所需要的)并使用集成测试来引导整个 Spring 上下文来测试其他东西 -持久性问题、事务等。

您不需要 Spring 来模拟一个类的协作者并使用 Mockito 进行简单的交互测试。

【讨论】:

  • “如果你能帮上忙,我的建议是不要混合 Spring 和 mocks”。如果您有设置器或使用 Spring 的 ReflectionTestUtils.setField() 方法,请手动注入您的模拟。如果您的服务在每次测试中都发生变化,则无需启动 spring 上下文
  • 您的建议帮助我弄清楚了一些事情。当我真正的意思是交互测试时,我误用了“集成测试”这个词。我最初选择不使用 spring 来注入我的模拟,所以我现在切换回该代码,只使用 spring 进行真正的集成测试。谢谢!
【解决方案3】:

在您的 @Before 方法中,请务必重置您的模拟对象。

@Before
public void setup(){
    Mockito.reset(validator);
}

【讨论】:

  • 由于某种原因,其中一项测试现已修复,但其他测试仍然失败(与之前的消息相同)。我在 setup 方法中放了 4 行,如下所示: Mockito.reset(userService.getValidator());也许它失败了,因为我使用的是吸气剂?我在 UserService 的测试类中只有一个属性,而不是 4 个单独的对象。
【解决方案4】:

您可以尝试在您的测试方法中调用 setDirty(true) 来重新加载 Spring 上下文。

【讨论】:

    【解决方案5】:

    我从未使用过 Mockito,但默认情况下 Spring-Beans 是 Singletons - 因此除非您在 Spring-Container 上调用 refresh(),否则它们不会被重新创建。

    如果你无论如何都不需要它们是单例,你可以将它们的范围设置为prototype,这将在每次注入时创建新的 bean-instances...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-02
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 1970-01-01
      • 2012-10-07
      • 2011-04-16
      • 2013-03-20
      相关资源
      最近更新 更多