在测试中使用@MockBean 会强制重新加载应用程序上下文
Using @MockBean in tests forces reloading of Application Context
我在 Spring 框架上进行了几个集成测试 运行,扩展了名为 BaseITCase.
的基础 class
像这样:
@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 重新创建上下文,并且这个测试之后的测试有时会使用错误的 beans(来自为 @MockBean 测试创建的上下文)。我只是通过检查 bean 是否具有不同的哈希码才发现了这一点。
当我使用@EventListener 时,它变得非常重要。因为调用了错误上下文的侦听器(已经完成执行的测试 class 的上下文)并且我在那里有错误的 bean。
有什么解决方法吗?
我尝试将所有@MockBean 声明移动到基本 class 并且它工作正常,因为没有创建新的上下文。但是,它使基本 class 太重了。
另外,我试图为这个测试创建一个脏上下文,但是下一个测试失败并显示上下文已经关闭的消息。
原因是带有@MockBean的测试的spring配置与其余测试不同,所以spring框架无法缓存以前使用过的上下文,需要加载它再次。在这里你可以找到更详细的解释:https://github.com/spring-projects/spring-boot/issues/10015
如您所说,如果将模拟 bean 移动到父级 class,上下文不会重新加载,这很有意义,因为 bean 配置保持不变。
一种可能的解决方法是将您的 mock bean 定义为一个简单的 mock,然后在需要的地方手动注入它。
例如,UserController
依赖于 Foo
:
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;
}
}
希望这对您有所帮助。
@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;
}
免责声明:我创建这个库正是为了这个目的:在 spring beans 中模拟 beans 并避免冗长的上下文重新创建。
我在 Spring 框架上进行了几个集成测试 运行,扩展了名为 BaseITCase.
的基础 class
像这样:
@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 重新创建上下文,并且这个测试之后的测试有时会使用错误的 beans(来自为 @MockBean 测试创建的上下文)。我只是通过检查 bean 是否具有不同的哈希码才发现了这一点。
当我使用@EventListener 时,它变得非常重要。因为调用了错误上下文的侦听器(已经完成执行的测试 class 的上下文)并且我在那里有错误的 bean。
有什么解决方法吗?
我尝试将所有@MockBean 声明移动到基本 class 并且它工作正常,因为没有创建新的上下文。但是,它使基本 class 太重了。 另外,我试图为这个测试创建一个脏上下文,但是下一个测试失败并显示上下文已经关闭的消息。
原因是带有@MockBean的测试的spring配置与其余测试不同,所以spring框架无法缓存以前使用过的上下文,需要加载它再次。在这里你可以找到更详细的解释:https://github.com/spring-projects/spring-boot/issues/10015
如您所说,如果将模拟 bean 移动到父级 class,上下文不会重新加载,这很有意义,因为 bean 配置保持不变。
一种可能的解决方法是将您的 mock bean 定义为一个简单的 mock,然后在需要的地方手动注入它。
例如,UserController
依赖于 Foo
:
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;
}
}
希望这对您有所帮助。
@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;
}
免责声明:我创建这个库正是为了这个目的:在 spring beans 中模拟 beans 并避免冗长的上下文重新创建。