在测试中使用@MockBean强制重新加载应用程序上下文

我在Spring Framework上运行了几个集成测试,这些测试扩展了名为BaseITCase的基类。
喜欢这个:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppCacheConfiguration.class, TestConfiguration.class}, loader = SpringBootContextLoader.class)
@Transactional
@WebMvcTest
public abstract class BaseITCase{...}
...
public class UserControllerTest extends BaseITCase {...}

问题在于,其中一个测试有几个声明:@MockBean内部,执行此测试的那一刻,Spring会重新创建上下文,而在此测试之后的测试有时会使用错误的bean(从为测试创建的上下文@MockBean)。我只是通过检查豆子具有不同的哈希码才发现的。

当我使用@EventListener时,它变得非常关键。因为调用了错误上下文(已完成执行的测试类的上下文)的侦听器,而我在那里有错误的bean。

有什么解决方法吗?

我试图将所有@MockBean声明移动到基本类,它工作正常,因为没有创建新的上下文。但是,它使基本类太重了。另外,我试图为此测试创建一个脏上下文,但随后下一个测试失败,并显示上下文已关闭的消息。


答案 1

原因是具有@MockBean的测试的spring配置与其他测试不同,因此spring框架无法缓存以前使用的上下文,需要再次加载它。在这里你可以找到更详细的解释:https://github.com/spring-projects/spring-boot/issues/10015

正如你所说,如果你将模拟bean移动到父类,上下文不会被重新加载,这是有道理的,因为bean配置保持不变。

一种可能的解决方法是将模拟 Bean 定义为简单的模拟,并在需要时手动注入它。

例如,依赖于 :UserControllerFoo

public class UserControllerTest extends BaseITCase {

    private Foo foo = Mockito.mock(Foo.class);

    @Autowired
    private UserController userController;

    @Before
    public void setUp() {
        super.setup();

        this.userController.setFoo(foo);
    }
}

@Component
public class UserController {

    private Foo foo;

    @Autowired
    public void setFoo(final Foo foo) {
        this.foo = foo;
    }
}

希望这有帮助。


答案 2

@MockBean可能会导致上下文重新加载,如前面的答案中所述

作为替代方案,如果您使用的是spring boot 2.2 +,则可以使用@MockInBean而不是.它可以保持上下文的清洁,并且不需要重新加载上下文。@MockBean

@SpringBootTest
public class UserControllerTest extends BaseITCase {

    @MockInBean(UserController.class)
    private Foo foo;

    @Autowired
    private UserController userController;

    @Test
    public void test() {
        userController.doSomething();
        Mockito.verify(foo).hasDoneSomething();
    }
}

@Component
public class UserController {

    @Autowired
    private Foo foo;

}

免责声明:我创建这个库就是为了这个确切的目的:在春天的豆子中模拟豆子,避免冗长的上下文娱乐。


推荐