@webMvcTest is not excluding and loading beans marked as @Repository
Asked Answered
D

4

15

I've a @RestController which has only one dependency in field @Autowire that dependency is @component, that component Class definition has some autowired fields which are @service and those services have some @repositories.

In this whole flow I've kafka, Quartz, Cassandra and DB2 So when I was creating a Unit test case for my controller, I dont want to setup whole application. so I decided to use @webMvcTest and used @MockBean on my only one dependency of controller class.

But my Test is throwing and exception because its trying to create a Dao bean, which is marked as @repository.

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

    @MockBean
    MyControllerDependency dependency;

    @Autowired
    MockMvc mockMvc;

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

Here is oversimplified version of code

@Component
class MyControllerDependency { 
    @AutoiWired
    MyCustomService service;
}

@Service
class MyCustomService{

   @Autowired
   MyCustomDao dao;
}

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

I'm getting following exception in test.

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.

Question is, When I'm using @WebMvcTest slice and already mocking the only required dependency MyControllerDependency then why spring test context is trying to load MyCustomDao which is annotated as @Repository.

I can do integration testing with SpringbootTest & AutoconfigureMockMVC, but for writing Junit test just for controller, I need to use WebMvcTest slice. which is creating a problem.

Distill answered 26/10, 2020 at 16:16 Comment(0)
H
9

Do you have any @ComponentScan("...") annotation active on your @SpringBootApplication?

As described in 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.

One solution is to create a seperate @Configuration that is annotated with @ComponentScan. When creating a @WebMvcTest the configuration (and its component scan is ignored).

@Configuration
@ComponentScan("com.example.another")
public class DbConfig {
}
Herr answered 9/5, 2022 at 13:34 Comment(0)
H
28

I ran into a similar problem where I want to test only my controller using @WebMvcTest, but the spring context was trying to create nondependent spring beans and was failing as below.

Failed to load ApplicationContext java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'TestController' defined in file ...

Solution: load only the controller your testing for an example like @ContextConfiguration(classes = DemoController.class). Also, find below a complete sample


    @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());
        }
    }
}
Hamal answered 8/6, 2021 at 8:41 Comment(7)
Thanks, I lost access to that code repo due to client change :) But I'll test the similar scenario and let you know how it goes... Thanks for the update thoughDistill
why in the freaking world @ContextConfiguration(classes = DemoController.class) works and @WebMvcTest(controllers = DemoController.class) does not, is beyond me. This was really helpful. thank you.Kessler
@Kessler Did you figure out why ContextConfiguration works but WebMvcTest does not?Volplane
@JustvanderVeeken I did not, sorry mate.Kessler
@JustvanderVeeken @Kessler Found here on spring's source and debugging. At class/method SpringBootTestContextBootstrapper.buildTestContext, if you don't use ContextConfiguration then at TestContext instance, field mergedContextConfiguration.classes it will point to your app's main class instead of your controller, and thus it will use set-up of your main class (at least to some extent).Reviere
why excludeFilters in @WebMvcTest not work as expected?Universality
This should be accepted answer as question is around WebMvcTestMulloy
H
9

Do you have any @ComponentScan("...") annotation active on your @SpringBootApplication?

As described in 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.

One solution is to create a seperate @Configuration that is annotated with @ComponentScan. When creating a @WebMvcTest the configuration (and its component scan is ignored).

@Configuration
@ComponentScan("com.example.another")
public class DbConfig {
}
Herr answered 9/5, 2022 at 13:34 Comment(0)
E
5

This usually happens when you have explicit @ComponentScan annotation on the spring boot main application class.

The @ComponentScan annotation suppresses the default component scanning mechanism that happens with @Webmvctest where it scans up the package hierarchy and apply excludeFilters to find only controller and its related classes.

Extenuatory answered 26/10, 2020 at 18:29 Comment(1)
Thanks @Kumar_V, Yeah my internal company framework has a default microservice Application class, which all microservice application extends,,,, and this parent class has component scan defined for com.company.domain. and I cant remove it . Is there still a workaround to use the slice testing ?Distill
R
0

When you mock your bean using @MockBean annotation you should define what that mocked bean should do when you call it's method, you usually do this using when in Mockito. In your case it can be done this way:

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

At this line:

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

Instead of dependency.sample() you should put whatever method of MyControllerDependency class your controller call when you send a GET request to /api/test/restpoint path and with thenReturn("Hello, Mock") you define what is the mocked output of that method (when it gets called by your controller in your Unit test).

Raising answered 26/10, 2020 at 17:54 Comment(2)
Thanks for reponse, but that is not the problem at all. for your sake I'll update the MockBehaviour line... the problem I requested is related to testcase spring context startup... so even if I delete all the code in the Test method and do assert(true) it will still failDistill
I updated the question, to avoid the distraction due to multiple informationDistill

© 2022 - 2024 — McMap. All rights reserved.