3

Given production code classes:

@RestController
@RequiredArgsConstructor
public class MyController {

    private final MyValidator validator;

    // annotations relating to request mapping excluded for brevity 
    public void test(@Valid @RequestBody final MyParams params) {
        // do stuff
    }

    @InitBinder
    @SuppressWarnings("unused")
    protected void initBinder(final WebDataBinder binder) {
        binder.setValidator(validator);
    }
}

and

@Component
@RequiredArgsConstructor
public class MyValidator implements Validator {

    ...

    @Override
    public void validate(final Object target, final Errors errors) {
        // custom validation
    }
}

and finally test code:

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {
    // tests
}

I encounter the error:

NoSuchBeanDefinitionException: No qualifying bean of type 'MyValidator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

I think the error is fair enough. I've annotated the test as a WebMvcTest, which I believe has excluded @Component beans. This is intentional and desired (from the perspective that I am only wanting to test the "web layer", not the whole context - it just so happens I need a component which is related/used only in the controllers)

My question, therefore, is: how can one explicitly include a component like a validator in the test context for a web test?

My environment is java version "10.0.2" 2018-07-17, spring boot 1.5.16.RELEASE.

David
  • 7,652
  • 21
  • 60
  • 98

3 Answers3

3

There are two ways to solve this.

  1. Using @SpringBootTest and @AutoConfigureMvc instead of @RunWith(SpringRunner.class) and @WebMvcTest.

    @SpringBootTest
    @AutoConfigureMvc
    public class MyControllerTest {
    
    }
    
  2. Creating a @TestConfiguration class that injects the 'MyValidator' bean as:

        @RunWith(SpringRunner.class)
        @WebMvcTest(MyController.class)
        public class MyControllerTest {
           @TestConfiguration
           static class TestConfig {
              @Bean
              MyValidator getMyValidator(){
                  return new MyValidator();
              }
           }
           // tests
        }
    

    More on this can be found here : https://mkyong.com/spring-boot/spring-boot-how-to-init-a-bean-for-testing/

arvindkgs
  • 373
  • 4
  • 12
2

There are two ways to test the web layer

first.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyControllerTest {
  @Autowired
  private MyController myController;
}

The @SpringBootTest annotation tells Spring Boot to go and look for a main configuration class (one with @SpringBootApplication for instance), and use that to start a Spring application context.

A nice feature of the Spring Test support is that the application context is cached in between tests, so if you have multiple methods in a test case, or multiple test cases with the same configuration, they only incur the cost of starting the application once. You can control the cache using the @DirtiesContext annotation.

Secondly, if you want to use the @WebMvcTest(MyController.class)

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {

  @MockBean
  private MyValidator validator;

}

But this validator is a fake, so you have to customize it for testing.

See this link for more details https://spring.io/guides/gs/testing-web/

derekpark
  • 147
  • 4
  • Is it true to say that the first approach detailed above would likely (perhaps depending on other configuration found on the `SpringBootApplication` class) be starting the entire spring context (all services, repositories, components, etc...). The second approach is what I'm already doing - I could use a fake validator and write separate unit tests for the validator logic to cover the implementation. I'll also experiment with having a `WebMvcConfigurerAdapter` define the beans - as i think these beans would then apply in the context. – David Oct 17 '18 at 08:36
2

I cannot recommend it as a standard practice but if you do need an instance of a dependency in your Web MVC tests (for example in legacy code) you can add them into the spring context using @SpyBean annotation.

Real methods of that class will be called during the test and you can verify them if needed similarly to the beans annotated with @MockBean

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {

    @SpyBean
    private MyValidator validator
}
Alexander Pranko
  • 1,859
  • 17
  • 20