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]
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 ;-).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With