问题定位
@MockBean或@SpyBean会改变bean在context中的状态,为了保证每个test运行时context不被污染而进行刷新,属于正常现象,是springboot的默认行为。但由此会导致其他不可预料的错误。
springboot提供了spirng-boot-starter-test以供开发者使用单元测试,其中org.springframework.boot.test的mockito包,是springboot对org.mockito的增强替换,@MockBean和@SpyBean是主要的mock注解。
tests使用@MockBean和@SpyBean会导致bean在spring test context中缓存的cache key变化,springboot默认缓存context,当顺序执行的两个tests分别依赖不同的但需要被mock的bean或者同一个bean而在其中一个test中需要被mock时(或者其他会污染context的行为),spring test context发生变化,进而引起运行第二个test前刷新context。
于是多个tests重复启动spring test context(较早的context不会自动关闭?jvm中已存在class meta?),不能保证每个tests的执行上下文的独立性、隔离性,某一些bean在被多次创建后会出现异常。
参考issue
Spring boot test: context loaded for every test?
Context not being reused in tests when MockBeans are used
Context isn’t cached when MockBean using
@SpyBean makes context restart between two tests
文档释义
Mocking and Spying Beans
Context Management and Caching
解决方案
快速方案
一、对于非web工程
将用例中使用的mock变量放到父类,这样所有的用例类都使用同一个上下文,具体的mock放到实际的测试用例类上。
//测试父类
@RunWith(SpringJUnit4ClassRunner.class)
public class XxxTestBase {
@MockBean
private BeanService1 beanService1;
@MockBean
private BeanService2 beanService2;
@MockBean
private BeanService2 beanService2;
}
//具体的测试类
public class ConcreteTest extends XxxTestBase {
@Test
public void test() {
beanService2.doTest();
}
}
二、对于web工程
如果是web工程,spring context刷新会导致端口冲突,通过设置端口随机来避免。
@springbootTest(class=SOFABootTestApplication.class, WebEnironment=WebEnvironment.RANDOM_PORT)
参考文档:Testing Spring Boot Applications
可能潜在的问题
1.把该bean不需要被mock的方法一起mock了(@SpyBean替换@MockBean?);
2.一些场景下该bean不需要被mock,而上下文中该bean是被mock的;
3.web工程设置为端口随机,可能会导致某些依赖端口的用例出现失败;
相关阅读
Do You Really Need @DirtiesContext?
Annotation Type DirtiesContext
其他资料
Mockito.mock() vs @Mock vs @MockBean
Think Twice Before Using @MockBean