【问题标题】:How to use @MockBean with JUnit 5 in Spring Boot?如何在 Spring Boot 中将 @MockBean 与 JUnit 5 一起使用?
【发布时间】:2019-10-10 20:27:01
【问题描述】:

我正在尝试在 @WebMvcTest 中使用 @MockBean 和 JUnit 5 编写单元测试。不幸的是,@MockBean 似乎被忽略了,它尝试设置完整的持久层,但失败了,这不是我想要的单元测试。

据我了解,@WebMvcTest 根本不应该触发应用程序上下文的设置。它应该用于单元测试。所以问题可能是为什么要创建应用程序上下文?

主类:

@SpringBootApplication
@EntityScan("ch.xxx.infop.common.entity")
@EnableJpaRepositories("ch.xxx.infop.dao")
public class ApplicationDispatch {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationDispatch.class, args);
    }

}

调度控制器:

@RestController
public class DispatchController {

    @Autowired
    VmRepository vmRepository;

    @RequestMapping(path="/compress/{fpId}/{datenlieferantId}/{varianteTyp}")
    public String publishMessage(
            @PathVariable long fpId,
            @PathVariable long datenlieferantId,
            @PathVariable VarianteTyp varianteTyp) {

        List<Vm> vmList = vmRepository.findByFpIdAndDatenlieferantId(fpId, datenlieferantId);

        // ...

        return "success";
    }

}

DispatchUnitTest:

@ExtendWith(SpringExtension.class)
@WebMvcTest(DispatchController.class)
public class DispatchUnitTest {

    @MockBean
    private VmRepository vmRepository;

    @Autowired
    private MockMvc mvc;

    @BeforeEach
    public void setUp() {
        List<Vm> testVmList = new ArrayList<>();
        for (long i=0; i<3; i++) {
            Vm vm = new Vm();
            vm.setId(i);
            testVmList.add(vm);
        }

        when(vmRepository.findByFpIdAndDatenlieferantId(anyLong(), anyLong())).thenReturn(testVmList);
    }

    @Test
    public void contextLoads() throws Exception {
        mvc.perform(
                MockMvcRequestBuilders.get("/compress/2015/14/J"))
        .andExpect(status().isOk())
        .andExpect(content().string("success"));
    }

}

测试输出:

10:40:57.169 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration ch.xxx.infop.dispatch.ApplicationDispatch for test class ch.xxx.infop.dispatch.DispatchUnitTest
10:40:57.171 [main] DEBUG org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper - @TestExecutionListeners is not present for class [ch.xxx.infop.dispatch.DispatchUnitTest]: using defaults.
10:40:57.171 [main] INFO org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
10:40:57.191 [main] INFO org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@11c9af63, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@757acd7b, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@36b4fe2a, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@574b560f, org.springframework.test.context.support.DirtiesContextTestExecutionListener@ba54932, org.springframework.test.context.transaction.TransactionalTestExecutionListener@28975c28, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3943a2be, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@343570b7, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@157853da, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@71c3b41, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@236e3f4e, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@3cc1435c]
10:40:57.195 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@46fa7c39 testClass = DispatchUnitTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@1fb700ee testClass = DispatchUnitTest, locations = '{}', classes = '{class ch.xxx.infop.dispatch.ApplicationDispatch}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@4f67eb2a key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration, org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration, org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration, org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration, org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration, org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration, org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration, org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration, org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration, org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration, org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration, org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration, org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1cbbffcd, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@3d51f06e, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@df16af6c, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@498d318c, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@d8cd8528, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@f44e2ec2, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7e07db1f], resourceBasePath = '', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]], class annotated with @DirtiesContext [false] with mode [null].
10:40:57.342 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper=true, server.port=-1}

,------.   ,--.                            ,--.          ,--.
|  .-.  \  `--'  ,---.   ,---.   ,--,--. ,-'  '-.  ,---. |  ,---.  
|  |  \  : ,--. (  .-'  | .-. | ' ,-.  | '-.  .-' | .--' |  .-.  | 
|  '--'  / |  | .-'  `) | '-' ' \ '-'  |   |  |   \ `--. |  | |  | 
`-------'  `--' `----'  |  |-'   `--`--'   `--'    `---' `--' `--' 
                        `--'
2019-05-24 10:40:57.725  INFO 16844 --- [           main] ch.xxx.infop.dispatch.DispatchUnitTest   : Starting DispatchUnitTest on K57176 with PID 16844 (started by ue73011 in C:\devxxx\projekte\infop-dispatch)
2019-05-24 10:40:57.727  INFO 16844 --- [           main] ch.xxx.infop.dispatch.DispatchUnitTest   : No active profile set, falling back to default profiles: default
2019-05-24 10:40:58.248  INFO 16844 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-05-24 10:40:58.501  INFO 16844 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 238ms. Found 1 repository interfaces.
2019-05-24 10:40:59.589  INFO 16844 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2019-05-24 10:40:59.597  INFO 16844 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2019-05-24 10:40:59.604  INFO 16844 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2019-05-24 10:40:59.670  INFO 16844 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-05-24 10:40:59.702  INFO 16844 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.hateoas.config.HateoasConfiguration' of type [org.springframework.hateoas.config.HateoasConfiguration$$EnhancerBySpringCGLIB$$82ead0f5] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-05-24 10:41:00.624  WARN 16844 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'vmRepositoryImpl': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available
2019-05-24 10:41:00.630  INFO 16844 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-05-24 10:41:00.661 ERROR 16844 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

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

Description:

A component required a bean of type 'javax.persistence.EntityManagerFactory' that could not be found.


Action:

Consider defining a bean of type 'javax.persistence.EntityManagerFactory' in your configuration.

2019-05-24 10:41:00.667 ERROR 16844 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@36b4fe2a] to prepare test instance [ch.xxx.infop.dispatch.DispatchUnitTest@6ed043d3]

【问题讨论】:

标签: java spring-boot junit5 spring-boot-test


【解决方案1】:

经过大量谷歌搜索和尝试错误后,我找到了这个解决方案:

@SpringJUnitConfig
@WebMvcTest(DispatchController.class)
public class DispatchUnitTest {

    @Configuration
    static class Config {
        @Bean
        DispatchController dispatchController() {
            return new DispatchController();
        }
    }

    @MockBean
    private VmRepository vmRepository;

    @Autowired
    private MockMvc mvc;

    @BeforeEach
    public void setUp() {
        List<Vm> testVmList = new ArrayList<>();
        for (long i=0; i<3; i++) {
            Vm vm = new Vm();
            vm.setId(i);
            testVmList.add(vm);
        }

        when(vmRepository.findByFpIdAndDatenlieferantId(anyLong(), anyLong())).thenReturn(testVmList);
    }

    @Test
    public void contextLoads() throws Exception {
        mvc.perform(
                get("/compress/2015/14/J"))
                    .andExpect(status().isOk())
                    .andExpect(content().string("success"));
    }

}

主要变化是使用@SpringJUnitConfig而不是@ExtendWith(SpringExtension.class)。这增加了@ContextConfiguration 自动检测

@Configuration
static class Config {
    @Bean
    DispatchController dispatchController() {
        return new DispatchController();
    }
}

我不完全理解为什么这是必要的,以及我在问题中的代码的主要区别是什么。其实我发现很多例子都没有这个配置。

因此,这个问题仍有获得荣耀的空间;-)。

【讨论】:

  • 您遇到问题的原因是主应用程序类用于加载切片测试,例如WebMvcTestDataJpaTest 等。您在主应用程序上定义的任何内容都将被加载,因此WebMvcTest 正在尝试配置 JPA 存储库并扫描实体。除非它们在不同的包中,否则不需要这些注释,如果它们是,请将它们移动到单独的 @Configuration 类以扫描它们
  • 我相信这是创可贴,@Darren Forsythe 的评论是正确的解决方案
【解决方案2】:

我无法在本地重现该问题,但要测试控制器层,您只需要使用@WebMvcTest(不需要@SpringJUnitConfig)然后使用@MockBean 模拟组件。这是一个例子:

@WebMvcTest(TodoController.class)
public class TodoControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;
    // ....
}

您可以从here查看完整代码。

article 解释和总结了用于每个测试场景的注释。

【讨论】:

    猜你喜欢
    • 2019-09-07
    • 2017-11-09
    • 2021-02-16
    • 2018-10-23
    • 1970-01-01
    • 2017-09-02
    • 2019-04-27
    • 2020-11-02
    • 2020-04-10
    相关资源
    最近更新 更多