当 运行 系列测试时,MockBean 似乎被 Scoped Proxy Bean 忽略
MockBean seemingly being ignored for Scoped Proxy Bean when running tests in series
我正在为一个 Spring 引导 MVC 控制器和一些会话范围的 bean 进行单元测试,虽然应用程序按预期工作,但由于未知原因单元测试被证明很麻烦,我我想进一步了解原因。
bean 配置如下所示:
@Bean(name="scopedBean")
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyScopedBean myScopedBean() {
return new MyScopedBean();
}
控制器看起来像这样:
@Autowired
private ISomeService service;
@Autowired
private MyScopedBean scopedBean;
@GetMapping("/")
public ModelAndView getRequest(ModelAndView model, @RequestParam("id") UUID id) {
if(scopedBean.matches(id)){
//do some stuff
}
//return a page
}
class 中还有其他方法可以获取、设置和检查 scopedBean 中的值。
测试看起来像这样:
@WebMvcTest(MyController.class)
@Import({ActualValidator.class,SecurityConfig.class})
class MyControllerTest {
@MockBean
private MyUserDetailsService userDetailsService;
@MockBean
private ISomeService someService;
@MockBean
private MyScopedBean scopedBean;
@Test
void testA() throws Exception {
when(scopedBean.getAValue()).thenReturn("myvalue");
when(scopedBean.matches("someProperty").thenReturn(false);
mockMvc.perform(get("/"))
.andExpect(view().name("something");
}
@Test
void testB() throws Exception {
when(scopedBean.getAnotherValue()).thenReturn("othervalue");
when(scopedBean.matches("anotherProperty").thenReturn(true);
mockMvc.perform(get("/"))
.andExpect(view().name("somethingelse");
}
}
每个测试,当 运行 单独时,通过。当 运行 它们全部在一起时,它们不仅会失败,它们还会 return 来自 模拟代理 内部的空指针,就好像该对象已被正确实例化一样。我可能误解了 @MockBean
的概念,但我的理解是不应该实际实例化该对象?这可能是因为它是一个实际对象而不是接口吗?
我已经尝试配置 la Baeldung,尽管文章将 运行 讨论为 @SpringBootTest
而不是剥离后的 MVC 测试。将该配置添加为上下文配置会导致所有测试开始 return 为 redirectedUrls、视图等设置空值,就好像它没有正确设置一样 - 它可能不是。
关于我的测试有时看起来如何使用非模拟对象,是否有什么非常明显的东西是我遗漏的?
编辑:
剧情变厚了!只是为了好玩,我决定在一些打印出 bean 值的控制器方法的开头添加一个 sysout,希望获得内存地址。
对于每个通过的测试,它都会打印 scopedBean bean
。
对于失败的测试,它打印一个实际的toString a la MyScopedBean(valueA=null, valueB=null, valueC=null)
- 这似乎...错误.说我疯了,但这听起来像是在那里创建了一个对象?
编辑 2:
我已经 raised this with the Spring team - at first glance it may look related to this issue,但是删除 @SessionScope
并不能解决问题。添加 mockito-inline 也没有。切换到 @SpyBean
会导致稍微更一致 - 但 doReturn
似乎仍被忽略。
所以解决方案 是 为静态方法添加 mockito-inline
- 但不幸的是,我还在我的控制器下面犯了一个错误,这才是真正的罪魁祸首。
任何创建为 @MockBean
的内容仍然可以被覆盖 - 所以在我的方法之一中我已经完成了:
scopedBean = new ScopedBean();
...“重置”它。在实践中工作正常 - 除了当您测试时,这意味着模拟被替换为 bean 的真实版本,并且因为测试重用相同的上下文,“模拟”将无用。
我反而创建了一个方法来重置它而不重新实例化。 mockito-inline
不再需要。
我正在为一个 Spring 引导 MVC 控制器和一些会话范围的 bean 进行单元测试,虽然应用程序按预期工作,但由于未知原因单元测试被证明很麻烦,我我想进一步了解原因。
bean 配置如下所示:
@Bean(name="scopedBean")
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyScopedBean myScopedBean() {
return new MyScopedBean();
}
控制器看起来像这样:
@Autowired
private ISomeService service;
@Autowired
private MyScopedBean scopedBean;
@GetMapping("/")
public ModelAndView getRequest(ModelAndView model, @RequestParam("id") UUID id) {
if(scopedBean.matches(id)){
//do some stuff
}
//return a page
}
class 中还有其他方法可以获取、设置和检查 scopedBean 中的值。
测试看起来像这样:
@WebMvcTest(MyController.class)
@Import({ActualValidator.class,SecurityConfig.class})
class MyControllerTest {
@MockBean
private MyUserDetailsService userDetailsService;
@MockBean
private ISomeService someService;
@MockBean
private MyScopedBean scopedBean;
@Test
void testA() throws Exception {
when(scopedBean.getAValue()).thenReturn("myvalue");
when(scopedBean.matches("someProperty").thenReturn(false);
mockMvc.perform(get("/"))
.andExpect(view().name("something");
}
@Test
void testB() throws Exception {
when(scopedBean.getAnotherValue()).thenReturn("othervalue");
when(scopedBean.matches("anotherProperty").thenReturn(true);
mockMvc.perform(get("/"))
.andExpect(view().name("somethingelse");
}
}
每个测试,当 运行 单独时,通过。当 运行 它们全部在一起时,它们不仅会失败,它们还会 return 来自 模拟代理 内部的空指针,就好像该对象已被正确实例化一样。我可能误解了 @MockBean
的概念,但我的理解是不应该实际实例化该对象?这可能是因为它是一个实际对象而不是接口吗?
我已经尝试配置 la Baeldung,尽管文章将 运行 讨论为 @SpringBootTest
而不是剥离后的 MVC 测试。将该配置添加为上下文配置会导致所有测试开始 return 为 redirectedUrls、视图等设置空值,就好像它没有正确设置一样 - 它可能不是。
关于我的测试有时看起来如何使用非模拟对象,是否有什么非常明显的东西是我遗漏的?
编辑:
剧情变厚了!只是为了好玩,我决定在一些打印出 bean 值的控制器方法的开头添加一个 sysout,希望获得内存地址。
对于每个通过的测试,它都会打印 scopedBean bean
。
对于失败的测试,它打印一个实际的toString a la MyScopedBean(valueA=null, valueB=null, valueC=null)
- 这似乎...错误.说我疯了,但这听起来像是在那里创建了一个对象?
编辑 2:
我已经 raised this with the Spring team - at first glance it may look related to this issue,但是删除 @SessionScope
并不能解决问题。添加 mockito-inline 也没有。切换到 @SpyBean
会导致稍微更一致 - 但 doReturn
似乎仍被忽略。
所以解决方案 是 为静态方法添加 mockito-inline
- 但不幸的是,我还在我的控制器下面犯了一个错误,这才是真正的罪魁祸首。
任何创建为 @MockBean
的内容仍然可以被覆盖 - 所以在我的方法之一中我已经完成了:
scopedBean = new ScopedBean();
...“重置”它。在实践中工作正常 - 除了当您测试时,这意味着模拟被替换为 bean 的真实版本,并且因为测试重用相同的上下文,“模拟”将无用。
我反而创建了一个方法来重置它而不重新实例化。 mockito-inline
不再需要。