6

I have multiple controllers and my understanding is that by specifying one controller in @WebMvcTest that other controllers wouldn't be loaded into context. From the docs

controllers - Specifies the controllers to test. May be left blank if all @Controller beans should be added to the application context.

My first Controller

@Controller
public class MyController {

    @Autowired
    private MyService myService;

    private final Logger logger = Logger.getLogger(this.getClass());

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> index() {
        try {
            myService.get();
            return new ResponseEntity<String>(HttpStatus.OK);
        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();
        }
        return new ResponseEntity<String>("REQUEST FAILED", HttpStatus.INTERNAL_SERVER_ERROR);
    }

}

My other controller

@Controller
public class MyOtherController {

    @Autowired
    private MyOtherService myOtherService;

    etc...
}

My Test for my Controller

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = { MyController.class }, secure = false)
@ActiveProfiles({ "test" })
public class MyControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    MyService myService;

    @Test
    public void testBaseReq() throws Exception {
        Testing dummyData = new Testing();
        dummyData.setData("testing");
        when(myService.get(anyInt())).thenReturn(dummyData);

        this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk());
    }
}

But when I run this test, it fails trying to load the bean MyOtherService from MyOtherContoller when loading the context.

2017-09-28 11:50:11.687 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'myOtherController'
2017-09-28 11:50:11.687 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating instance of bean 'myOtherController'
2017-09-28 11:50:11.687 DEBUG 16552 --- [           main] o.s.b.f.annotation.InjectionMetadata     : Registered injected element on class [my.package.other.myOtherController]: AutowiredFieldElement for private my.package.other.myOtherService my.package.other.myOtherController.myOtherService
2017-09-28 11:50:11.687 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Eagerly caching bean 'myOtherController' to allow for resolving potential circular references
2017-09-28 11:50:11.687 DEBUG 16552 --- [           main] o.s.b.f.annotation.InjectionMetadata     : Processing injected element of bean 'myOtherController': AutowiredFieldElement for private my.package.other.myOtherService my.package.other.myOtherController.myOtherService
2017-09-28 11:50:11.688 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'myOtherService'
2017-09-28 11:50:11.688 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating instance of bean 'myOtherService'
2017-09-28 11:50:11.689 DEBUG 16552 --- [           main] o.s.b.f.annotation.InjectionMetadata     : Registered injected element on class [my.package.other.myOtherService]: AutowiredFieldElement for private my.package.other.myOtherMapper my.package.other.myOtherService.myOtherMapper
2017-09-28 11:50:11.689 DEBUG 16552 --- [           main] o.s.b.f.annotation.InjectionMetadata     : Registered injected element on class [my.package.other.myOtherService]: AutowiredFieldElement for private ie.aib.services.coredemo.FinancialsRegionService my.package.other.myOtherService.financialsRegionService
2017-09-28 11:50:11.689 DEBUG 16552 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Eagerly caching bean 'myOtherService' to allow for resolving potential circular references
2017-09-28 11:50:11.689 DEBUG 16552 --- [           main] o.s.b.f.annotation.InjectionMetadata     : Processing injected element of bean 'myOtherService': AutowiredFieldElement for private my.package.other.myOtherMapper my.package.other.myOtherService.myOtherMapper
2017-09-28 11:50:11.690  WARN 16552 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myOtherController': Unsatisfied dependency expressed through field 'myOtherService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myOtherService': Unsatisfied dependency expressed through field 'myOtherMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'my.package.other.myOtherMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

I thought specifying the controller to test in the WebMvcTest annotation would limit it to the only loading that contoller. But its trying to load the other controller and because its beans aren't mocked it fails.

What am I missing or is my understanding incorrect? I think I should only have to mock beans for the Controller under test. I've also tried an excludeFilter to specifically exclude the package for the other Controller but that didn't change the error.

MadMurf
  • 2,247
  • 3
  • 23
  • 30

2 Answers2

9

Please make sure the Application.class your test picks up, doesn't contain @ComponentScan annotation. For example, this is your package structure

abc-project
  +--pom.xml
  +--src
    +-- main
      +-- com
        +-- abc
          +-- Application.java
          +-- controller
            +-- MyController.java
    +-- test
      +-- com
        +-- abc
          +-- Application.java
          +-- controller
            +-- MyControllerTest.java

The Application.java under test should look similar to this example,

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}
Indra Basak
  • 7,124
  • 1
  • 26
  • 45
  • Thanks @Indra, I think that's my issue alright. I have a \@ComponentScan as I need to explicitly filter to exclude some Components in an included jar. I'm looking now to change that so I have a separate Application class when running WebMvcTests – MadMurf Oct 02 '17 at 23:30
  • Thanks, you made my day. I was getting a different error (Spring tried to instantiate a bean buried down in the context, that shouldn't be instantiated in the test) and your advice worked for me as well. – Antonio Nov 17 '17 at 10:41
  • 1
    I'm having the same issue, but unfortunately, this solution doesn't work for me. The `@SpringBootApplication` annotation comes with `@ComponentScan` if I remove `@ComponentScan` every test started giving 404 – Ivan Vilanculo Nov 06 '20 at 16:05
  • @IvanVilanculo Without seeing how your project is structured, it would be difficult for me to comment – Indra Basak Nov 08 '20 at 19:37
0

my.package.other.myOtherMapper (probably Mybatis Mapper file) is missing or not clearly initialized.

As of my understanding, myOtherService implementation class has Mapper file which is not mapped properly.

You may have to map them first. You can post the Mapper xml content if possible.

  <context:component-scan base-package="org.example">       
    <context:exclude-filter type="custom" expression="abc.xyz.MyOtherController"/>
  </context:component-scan>
Raja CSP
  • 172
  • 7
  • Yes myOtherMapper is not intiialiased but its trying to instantiate it because its trying to load MyOtherController into context. If that controller was excluded from context, which I think it should be, then myOtherService wouldn't be looked for and hence myOtherMapper wouldn't be looked for. – MadMurf Sep 28 '17 at 03:11
  • I could get around it by adding @MockBean in the test for MyOtherService but I shouldn't have to mock every service to test a Controller that only uses one service. – MadMurf Sep 28 '17 at 03:14
  • try this https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/beans.html#beans-scanning-filters – Raja CSP Sep 28 '17 at 03:18
  • thanks but I already tried an exclude filter to ignore the package for the other controller, didn't make a difference – MadMurf Sep 28 '17 at 03:23
  • https://stackoverflow.com/questions/13034515/how-to-exclude-a-class-from-spring-in-applicationcontext you can exclude a specific class as per this answer. You might have issues on other area – Raja CSP Sep 28 '17 at 03:38
  • But if I have to specifically exclude every other class thats a controller I'll be forever modifying my unrelated Unit test as I add functionality. The questions is why Spring Boot context is loading the other controller when WebMvcTest docs seem to indicate it shouldn't – MadMurf Sep 28 '17 at 03:56
  • Did you try on annotation? sample: @WebMvcTest(controllers = MyController.class, excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.foo.bar.*")}) https://stackoverflow.com/questions/40627573/how-to-exclude-packages-from-a-context-using-webmvctest – Raja CSP Sep 28 '17 at 04:32