@webMvcTest 不排除和加载标记为 @Repository 的 bean

@webMvcTest is not excluding and loading beans marked as @Repository

我有一个 @RestController,它在字段 @Autowire 中只有一个依赖项 该依赖项是@component,该组件Class定义有一些自动装配的字段,它们是@service,那些服务有一些@repositories。

在整个流程中,我使用了 kafka、Quartz、Cassandra 和 DB2 因此,当我为我的控制器创建单元测试用例时,我不想设置整个应用程序。所以我决定使用 @webMvcTest 并在我唯一的控制器依赖项上使用 @MockBean class.

但是我的测试抛出异常,因为它试图创建一个标记为 @repository 的 Dao bean。

@ActiveProfiles("test")
@WebMvcTest(controllers = MyControllerTest .class)
class MyControllerTest {

    @MockBean
    MyControllerDependency dependency;

    @Autowired
    MockMvc mockMvc;

    @Test
    void test_something() throws Exception {
       assert(true);
    }
}

这里是代码的简化版

@Component
class MyControllerDependency { 
    @AutoiWired
    MyCustomService service;
}

@Service
class MyCustomService{

   @Autowired
   MyCustomDao dao;
}

@Repository
class MyCustomDao{
    @Autowired
    private JdbcTemplate template;
}

我在测试中遇到以下异常。

Exception

***************************
APPLICATION FAILED TO START
***************************

Description:

Field template in com.....MyCustomDao`  required a bean of type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.

问题是,当我使用 @WebMvcTest slice 并且已经模拟了唯一需要的依赖项 MyControllerDependency 那么为什么 spring 测试上下文试图加载 MyCustomDao注释为 @Repository.

我可以使用 SpringbootTestAutoconfigureMockMVC 进行集成测试,但是为了只为控制器编写 Junit 测试,我需要使用 WebMvcTest slice。这造成了一个问题。

当你使用 @MockBean 注释模拟你的 bean 时,你应该定义当你调用它的方法时模拟 bean 应该做什么,你通常在 Mockito 中使用 when 来做到这一点。在您的情况下,可以这样做:

@ActiveProfiles("test")
@WebMvcTest
class MyControllerTest {

    @MockBean
    MyControllerDependency dependency;

    @Autowired
    MockMvc mockMvc;

    @Test
    void test_something() throws Exception {
        when(dependency.sample()).thenReturn("Hello, Mock");
        mockMvc.perform(get("/api/test/restpoint")
                .accept(MediaType.APPLICATION_JSON))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

在这一行:

when(dependency.sample()).thenReturn("Hello, Mock");

而不是 dependency.sample() 你应该把 MyControllerDependency class 的任何方法放在你的控制器调用时,当你向 /api/test/restpoint 路径发送 GET 请求时thenReturn("Hello, Mock") 您定义该方法的模拟输出是什么(当它在您的单元测试中被您的控制器调用时)。

当您在 spring 引导主应用程序 class.

上有明确的 @ComponentScan 注释时,通常会发生这种情况

@ComponentScan 注释抑制了@Webmvctest 发生的默认组件扫描机制,它向上扫描包层次结构并应用 excludeFilters 以仅查找控制器及其相关 classes。

我 运行 遇到了一个类似的问题,我只想使用 @WebMvcTest 测试我的控制器,但是 spring 上下文试图创建非依赖性 spring beans 并且失败了下面。

无法加载 ApplicationContext java.lang.IllegalStateException: 无法加载 ApplicationContext 由以下原因引起:org.springframework.beans.factory.UnsatisfiedDependencyException:创建名称为 'TestController' 的 bean 在文件中定义时出错 ...

解决方案:仅加载您测试的控制器,例如 @ContextConfiguration(classes = DemoController.class)。 另外,在下面找到一个完整的示例

@WebMvcTest
@ContextConfiguration(classes = DemoController.class)  
public class DemoControllerTest {    
@Autowired
MockMvc mockMvc;

@MockBean
DemoService demoService;

@Test
public void testGetAllProductCodes_withOutData() throws Exception {
    
when(productCodeService.getAllProductCodes()).thenReturn(new ArrayList<ProductCodes>());
        mockMvc.perform(MockMvcRequestBuilders.get("/services/productCodes")).andExpect(MockMvcResultMatchers.status().isNoContent());
    }
}

}

您的 @SpringBootApplication 上是否有任何 @ComponentScan("...") 注释处于活动状态?

Spring Boot Reference Documentation所述:

Another source of confusion is classpath scanning. Assume that, while you structured your code in a sensible way, you need to scan an additional package. Your application may resemble the following code:

@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {

    // ...

}

Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. For instance, a @DataJpaTest seems to suddenly scan components and user configurations of your application. Again, moving the custom directive to a separate class is a good way to fix this issue.

一个解决方案是创建一个单独的 @Configuration,用 @ComponentScan 注释。创建 @WebMvcTest 配置时(及其组件扫描被忽略)。

@Configuration
@ComponentScan("com.example.another")
public class DbConfig {
}