@WebMvcTest是否需要@SpringBootApplication注释?



我的目标是将以前使用Spring Boot 1.3开发的Spring Boot应用程序迁移到最新的Spring Boot 1.4。该应用程序由几个maven模块组成,其中只有一个包含带有@SpringBootApplication注释的类。

迁移的一部分是使用@WebMvcTest注释来有效地测试控制器,这里我遇到了一个问题。

考虑一个来自Spring Boot github页面的示例应用程序。@WebMvcTest注释工作完美,因为,据我所知(在我做了几次测试之后),在@SpringBootApplication注释的主包中有一个类。请注意,对于我自己的@WebMvcTest测试,我遵循了与上面示例中相同的概念。

我看到的唯一区别是,在我的应用程序中,控制器类位于单独的maven模块中(没有@SpringBootApplication注释类),但有@Configuration和SpringBootConfiguration配置。如果我没有用@SpringBootApplication注释任何类,我总是在测试控制器时得到一个断言。我的断言与上面示例中的SampleTestApplication类修改为仅具有@EnableAutoConfiguration@SpringBootConfiguration注释(@SpringBootApplication不存在)时的断言相同:

getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests)  Time elapsed: 0.013 sec  <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
    at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
   at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)

我该如何处理?我应该总是有类注释与@SpringBootApplication为了运行@WebMvcTest测试?

编辑1:我做了一个小的maven项目,有2个模块和一个最小的配置。它就在这里。现在,我得到在另一个模块中定义的存储库的NoSuchBeanDefinitionException异常。如果我配置"full" @SpringBootApplication -一切都很好。

编辑2:我修改了编辑1的小测试项目,给出了一个原始问题。我正在使用不同的注释,并在配置类上添加了@ComponentScan,因为我怀疑bean没有正确注册。然而,我希望只有@Controller bean(在@WebMvcTest(…类)中定义)应该基于@WebMvcTest行为背后的魔法进行注册。

编辑3:Spring Boot项目问题

简短的回答:I believe so.

长答:

我认为@WebMvcTest需要找到SpringBootApplication配置,因为WebMvcTest的唯一目的是帮助简化测试(SpringBootApplication宁愿尝试加载整个世界)。

在你的具体情况下,因为你没有任何在你的非测试包,我相信它也找到了SampleTestConfiguration与@ScanPackages注释,并以某种方式加载每个bean。

src/main/java/sample/test

中添加以下内容
@SpringBootApplication
public class SampleTestConfiguration {
}

然后把你的测试改成:

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {
    @Autowired
    private MockMvc mvc;
    @MockBean
    private MyService ms;
    @Autowired
    private ApplicationContext context;
    @Test
    public void getDataAndExpectOkStatus() throws Exception {
        given(ms.execute("1")).willReturn(false);
        mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
    }
    @Test
    public void testMyControllerInAppCtx() {
        assertThat(context.getBean(MyController.class), is(not(nullValue())));
    }
    @Test
    public void testNoMyAnotherControllerInAppCtx() {
        try {
            context.getBean(MyAnotherController.class);
            fail("Bean exists");
        } catch (BeansException e) {
            // ok
        }
    }
}

@WebMvcTest找到SpringBootApplication,然后只加载有限数量的bean (参见文档):

@WebMvcTest将自动配置Spring MVC基础架构和将扫描的bean限制为@Controller, @ControllerAdvice, @JsonComponent,过滤器,webmvcconfigiler和handlermethoparumentresolver。常规的@Component bean在使用该注释时不会被扫描。

WebMvcTest需要SpringBootApplication: WebMvcTest继承了许多AutoConfiguration,所以需要SpringBoot来加载它们。然后它禁用许多其他自动配置,你的控制器变得容易测试。

使用WebMvcTest的全部意义是当您有SpringBootApplication并且您希望通过禁用除控制器之外的所有bean来使测试更简单时。如果你没有SpringBootApplication,那么为什么要使用WebMvcTest呢?

这是一个古老的话题,但有一个解决方案在这里没有提到。

您可以在您的测试源代码中创建一个带有SpringBootApplication注释的类。然后,您的项目仍然有一个很好的多模块结构,只有一个"真正的"SpringBootApplication

是的,根据spring boot文档

搜索算法从包含测试的包开始查找,直到找到一个带@SpringBootApplication或@SpringBootConfiguration注释的类。只要你以一种合理的方式构建你的代码,你的主配置通常是可以找到的。

但是在我开始使用@WebMvcTest之后,spring boot仍然尝试加载其他bean,最后TypeExcludeFilter成功了。

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = {JzYsController.class} )
    public class JzYsControllerTest {
        private static final String REST_V4_JZYS = "/rest/v4/JzYs";
        @Autowired
        private MockMvc mockMvc;
        @MockBean
        private JzYsService service;
        @Test
        public void deleteYsByMlbh() throws Exception {
            Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
            mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
            .andExpect(status().isNoContent());
        }
        @SpringBootConfiguration
        @ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
        public static class config{
        }
    }

还有一个解决方案。你不能使用@WebMvcTest,但可以通过构建器

自己配置MockMvc。

class TestControllerTest {
    private MockMvc mvc;
    @BeforeEach
    public void setup() {
        mvc = MockMvcBuilders.standaloneSetup(new TestController())
                .build();
    }
    @Test
    void test() throws Exception {
        // When
        var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));
        // Then
        res.andExpect(status().isOk());
    }
}     

但是这个解决方案可能会带来许多其他问题,例如配置问题、环境属性注入等。

最新更新