How to use @MockBean with JUnit 5 in Spring Boot?

11,419

Solution 1

After lots of googling and try and error I found this solution:

@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"));
    }

}

The main change is to use @SpringJUnitConfig instead of @ExtendWith(SpringExtension.class). This adds @ContextConfiguration which in terms autodetects

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

I don't fully understand why this is necessary and what's the main diffrence to my the code in the question. Actually I found a lot of examples which do not this configuration.

So there's still room to earn some glory with this question ;-).

Solution 2

I could not reproduce the issue locally but to test the controller layer you just need to use @WebMvcTest (do not need @SpringJUnitConfig) then use @MockBean to mock components. Here is an example:

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

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

You can view the full code from here.

This article explains and summarizes annotations using for each test scenario.

Share:
11,419
BetaRide
Author by

BetaRide

Developer, maker, youtuber. Visit my youtube channel: https://www.youtube.com/channel/UCN8wBEouFtaAyIjjIH2LjJQ

Updated on June 22, 2022

Comments

  • BetaRide
    BetaRide almost 2 years

    I'm trying to wirte a unit test with @MockBean and JUnit 5 in a @WebMvcTest. Unfortunately it looks like @MockBean is ignored and it tries to set-up the full persistence layer, which fails and which is not what I want for a unit test.

    As far as I understand, @WebMvcTest should not trigger the set-up of an application context at all. It's supposed to be used for unit tests. So the question is may be why an application context is create at all?

    Main Class:

    @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);
        }
    
    }
    

    DispatchController:

    @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"));
        }
    
    }
    

    Test output:

    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]
    
  • Darren Forsythe
    Darren Forsythe almost 5 years
    The reason you're getting the issues is the main application class is used to load up slice tests such as WebMvcTest, DataJpaTest etc. anything you have defined on the main application will be loaded, thus the WebMvcTest is trying to configure JPA Repositories and scan for enities. Unless they are in different packages these annotations are not needed if they are please move them to a seperate @Configuration class to scan for them
  • Lesiak
    Lesiak almost 5 years
    I believe this is band-aid, and comment by @Darren Forsythe is the correct solution