6

I have a simple health controller defined as follows:

@RestController
@RequestMapping("/admin")
public class AdminController {

    @Value("${spring.application.name}")
    String serviceName;

    @GetMapping("/health")
    String getHealth() {
        return serviceName + " up and running";
    }
}

And the test class to test it:

@WebMvcTest(RedisController.class)
class AdminControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void healthShouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/admin/health"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("live-data-service up and running")));
    }
}

When running the test, I'm getting the below error:

***************************
APPLICATION FAILED TO START
***************************

Description:

Field configuration in com.XXXX.LiveDataServiceApplication required a bean of type 'com.XXXXX.AppConfiguration' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.XXXX.AppConfiguration' in your configuration.

Here is AppConfiguration.java defined in the same package as the main spring boot app class:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties
public class AppConfiguration {

    @Value("${redis.host}")
    private String redisHost;

    @Value("${redis.port}")
    private int redisPort;

    @Value("${redis.password:}")
    private String redisPassword;
...
// getters and setters come here

Main class:

@SpringBootApplication
public class LiveDataServiceApplication {

    @Autowired
    private AppConfiguration configuration;

    public static void main(String[] args) {
        SpringApplication.run(LiveDataServiceApplication.class, args);
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(configuration.getRedisHost(), configuration.getRedisPort());
        redisConfiguration.setPassword(configuration.getRedisPassword());
        return new LettuceConnectionFactory(redisConfiguration);
    }
}

If I modify the annotation in the test class as follows, the test pass:

@SpringBootTest
@AutoConfigureMockMvc
class AdminControllerTest {
....

What am I missing?

Ryuzaki L
  • 37,302
  • 12
  • 68
  • 98
belgoros
  • 3,590
  • 7
  • 38
  • 76
  • You are doing autowired for `AppConfiguration` in `LiveDataServiceApplication` class. But you have not to market `AppConfiguration` class as a bean. – Deepak Kumar Aug 20 '20 at 15:36
  • I didn't get you, what do mean under _you have not to market AppConfiguration class as a bean_? – belgoros Aug 20 '20 at 15:37
  • You need to annotate the class `AppConfiguration` with any stereotype annotation like `@Service` – Deepak Kumar Aug 20 '20 at 15:41
  • I have already defined it with `@Configuration` which is aliased to `@Component`. Isn't it enough? – belgoros Aug 20 '20 at 15:42
  • No `@Configuration` is not an aliased to `@Component`. Both are having different use and purpose. – Deepak Kumar Aug 20 '20 at 15:44
  • Annotating a class with the "@Configuration" annotation indicates that the class will be used by JavaConfig as a source of bean definitions. An application may make use of just one "@Configuration"-annotated class, or many. Configuration can be considered the equivalent of XML's element. Like , it provides an opportunity to explicitly set defaults for all enclosed bean definitions. – belgoros Aug 20 '20 at 15:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/220153/discussion-between-belgoros-and-deepak-kumar). – belgoros Aug 20 '20 at 15:48

2 Answers2

7

You should understand the usage of @WebMvcTest and @SpringBootTest

@WebMvcTest : annotation is only to instantiates only the web layer rather than the whole context, so all dependencies in controller class should be mocked, you can look at the documentation

Spring Boot instantiates only the web layer rather than the whole context. In an application with multiple controllers, you can even ask for only one to be instantiated by using, for example, @WebMvcTest(HomeController.class).

We use @MockBean to create and inject a mock for the GreetingService (if you do not do so, the application context cannot start)

SpringBootTest : Spring boot test annotation actual load the application context for test environment

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

Ryuzaki L
  • 37,302
  • 12
  • 68
  • 98
  • 1
    So, why my `AppConfiguration` class is loaded when I start the app, but not when I run the test? Should I really keep a separate configuration class? Once it is removed and properties to read in `LiveDataServiceApplication` replaced, it worked in tests as well. – belgoros Aug 20 '20 at 15:59
  • I don't think it will work if you move them to separate package, please read my answer and attached documentation @belgoros – Ryuzaki L Aug 20 '20 at 16:01
  • No, I moved nothing, just removed AppConfiguration class – belgoros Aug 20 '20 at 16:03
  • If you remove it will work, since it don't need to inject `AppConfiguration` into main class, when you say `@WebMvcTest` it will only load the controller's and all other beans should be mocked – Ryuzaki L Aug 20 '20 at 16:05
  • Ok, are any docs about the use of a separate configuration class? Or we can declare any needed attribute with the « @Value » annotation no matter where in the project? – belgoros Aug 20 '20 at 16:09
  • sorry if you asking about `@Value` annotation, even it might not work with `@WebMvcTest`, just go through this once https://spring.io/guides/gs/testing-web/ – Ryuzaki L Aug 20 '20 at 16:11
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/220160/discussion-between-belgoros-and-deadpool). – belgoros Aug 20 '20 at 17:29
0

Define all properties in src/test/resource/application.file example to use junit 5 for rest layer:

@ExtendWith(MockitoExtension.class)
public class RestTest {

    
    @InjectMocks
    private RestClass  restClass;
    
    
    
    private MockMvc mockMvc;
    
    @BeforeEach
    public void init() throws Exception {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(restClass).build();
    }
    
    @Test
    public void test() throws Exception {
        String url = "/url";
        ResultActions resultActions = mockMvc.perform(get(url));
        resultActions.andExpect(status().isOk());

    }
    }
Manju D
  • 111
  • 2