当 运行 系列测试时,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 不再需要。