springunit-testingspring-bootcontroller

Spring Boot - Test for controller fails with 404 code


I want to write a test for the controller. Here is a test snippet:

@RunWith(SpringRunner.class)
@WebMvcTest(WeatherStationController.class)
@ContextConfiguration(classes = MockConfig.class)
public class WeatherStationControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private IStationRepository stationRepository;

    @Test
    public void shouldReturnCorrectStation() throws Exception {

        mockMvc.perform(get("/stations")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

controller code snippet:

@RestController
@RequestMapping(value = "stations")
public class WeatherStationController {

    @Autowired
    private WeatherStationService weatherService;

    @RequestMapping(method = RequestMethod.GET)
    public List<WeatherStation> getAllWeatherStations() {
        return weatherService.getAllStations();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public WeatherStation getWeatherStation(@PathVariable String id) {
        return weatherService.getStation(id);
    }

MockConfig class:

@Configuration
@ComponentScan(basePackages = "edu.repository")
public class MockConfig {

    //**************************** MOCK BEANS ******************************

    @Bean
    @Primary
    public WeatherStationService weatherServiceMock() {
        WeatherStationService mock = Mockito.mock(WeatherStationService.class);
        return mock;
    }

Here is error stack trace:

java.lang.AssertionError: Status 
Expected :200
Actual   :404

I can get what is wrong here.
How to fix the test for the controller?


Solution

  • HTTP code 404, means no resource found (on the server) for your request, which I think that your controller is not visible(let me say is not scanned) by spring boot.

    A simple solution is scanning a parent package in MockConfig class, so spring can pick up all beans,

    @ComponentScan(basePackages = "edu.lelyak") // assuming that's the parent package in your project
    

    if you don't like this approach, you can add the controller's package name in basePackages

    @ComponentScan(basePackages = {"edu.lelyak.controller","edu.lelyak.repository") 
    

    BTW, you don't have to manually set up WeatherStationService in MockConfig class, Spring boot can inject a mock for you and automatically reset it after each test method, you should just declare it in your test class:

    @MockBean
    private IStationRepository stationRepository;
    

    On the other hand, you should mock weatherService.getAllStations() before calling get("/stations") in your test method (as you're not running integration test), so you can do:

    List<WeatherStation> myList = ...;
    //Add element(s) to your list
     Mockito.when(stationService.getAllStations()).thenReturn(myList);
    

    You can find more in :