Spring Boot - Test for controller fails with 404 code
Asked Answered
F

12

27

I want to write a test for controller. Here is 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.lelyak.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 test for controller?

Frowzy answered 9/1, 2017 at 13:6 Comment(1)
Had a similar issue when I renamed the package and forgot to change in the @ComponentScan.Clarkclarke
S
24

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 :

Saying answered 8/8, 2017 at 23:20 Comment(1)
For me it turned out to be as simple as missing "/" in starting of the request path. I was trying "v1/user/{id}" instead of "/v1/user/{id}" in my controller test.Soche
P
35

I had the same issue. The controller was not getting picked up despite specifying it with @WebMvcTest(MyController.class). This meant all of its mappings were ignored, causing the 404. Adding @Import(MyController.class) resolved the issue, but I didn't expect the import to be necessary when I'm already specifying which controller to test.

Plott answered 6/7, 2020 at 15:39 Comment(8)
That doesn't make any sense but worked for me :D. I'm using Spring boot 2.4.4Pul
The same worked for me also with @ContextConfiguration(classes = {MyController.class)}Gleaning
well this works for my case, thanks for your pointer man, saved a headache for 2.1.6Santamaria
I think, ContextConfiguration does not add, but overwrites which classes are put into the context. So as soon as you use ContextConfiguration to add classes to the context (like some security configuration as in my case), you also have to add the controller under test in ContextConfiguration, despite being given in WebMvcTest already. Actually, you can leave out the controller from the WebMvcTest annotation and keep your code DRY.Parricide
However, in the end I added all classes to the value aka controllers attribute of WebMvcTest and removed the ContextConfiguration. At least with Spring 3.1.2, this works, although the documentation of the controllers attribute says that these should be the controllers under test.Parricide
Spring boot 3.1.X Adding import cause this A circular @Import has been detectedCellobiose
I am using spring boot '2.7.18' and this actually worked. I am so surprised though.Tropical
I've imported my controller. It doesn't work for me.Ceratoid
S
24

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 :

Saying answered 8/8, 2017 at 23:20 Comment(1)
For me it turned out to be as simple as missing "/" in starting of the request path. I was trying "v1/user/{id}" instead of "/v1/user/{id}" in my controller test.Soche
D
11

After some debugging, it appears that the target controller is simply not registered as a method handler. Spring scans the beans for the presence of RestController annotation.

But the problem is that the annotation could be found only if the bean is proxied via CGLIB, but for the case when we use WebMvcTest it's proxied by JDK.

As a result, I searched for the configuration which is responsible for making the choice, and the one was finally found AopAutoConfiguration. So when SpringBootTest is used this one is autoloaded when you need WebMvcTest+PreAuthorize in your controllers, then simply use:

@Import(AopAutoConfiguration.class)
Discreet answered 8/6, 2019 at 8:26 Comment(2)
The correct way to import autoconfiguration is using @ImportAutoConfiguration. Alternatively you can use @EnableAspectJAutoProxyAntilles
@Antilles @ImportAutoConfiguration doesn't work me, which makes sense as it is part of @WebMvcTest as well. @EnableAspectJAutoProxy didn't work for me either. In the end only @Import(AopAutoConfiguration.class) from this answer fixed the 404's on the controller with my @PreAuthorize-annotated endpoints.Desiderata
Q
9

I am not sure why your test is not working. But I got another solution which works for me.

@SpringBootTest
public class ControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build();
    }

    @Test
    public void shouldReturnCorrectStation() throws Exception {
        mockMvc.perform(get("/stations")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}
Quad answered 9/1, 2017 at 13:35 Comment(5)
how you looked to full controller test?Frowzy
@nazar_art I cloned you github project but it was full of errors. Not sure if your MockMvc is correct. I would go the easy way and register one controller in your test class like I provided.Quad
This app has exactly one controller. Also, you need to set up lombok support for your IDE. Without this, it wouldn't compile.Frowzy
For IntelliJ you need only install Lombok plugin and enable annotation processor at settings for the full project.Frowzy
The problem with @SpringBootTest is that it spins up the entire Spring environment. It's more suited to integration tests. That's why @WebMvcTest and other annotations exist. In addition, your tests will run a lot quicker.Chloropicrin
E
4

I import external configuration class by @ContextConfiguration(classes = MyConfig.class)

When I changed in MyConfig annotation @Configuration into @TestConfiguration it started to work properly.

Economizer answered 5/7, 2019 at 11:13 Comment(0)
A
3

I couldn't find a good answer but I could find one of the causes.

I was using in my tests the @PreAuthorize on the RestController.
You can mock the Oauth with this tip on the integration tests that use SpringBootTest. For SpringBootTest, this works very well too, but using SpringBootTest you load a lot of other resources (like JPA) that is not necessary to do a simple Controller test.

But with @WebMvcTest this not works as expected. The use of the WithMockOAuth2Scope annotation can be enough to stop the 401 error from authentication problem, but after that the WebMvcTest can't find the rest endpoint, returning the 404 error code.

After removing the @PreAuthorize on Controller, the test with WebMvcTest pass.

Alienable answered 27/4, 2018 at 16:24 Comment(0)
P
3

Based on the accepted answer, in my case I had copied and modified the file based on another test, but forgot to change the name for the controller on the top of the class, that being the reason why it was not finding the resource, as the error says.

@RunWith(SpringRunner.class)
@WebMvcTest(AnswerCommandController.class)
public class AnswerCommandControllerTest {
Prevost answered 26/1, 2021 at 3:35 Comment(0)
W
3

In case anyone is wondering.

If we don't use @ContextConfiguration, @WebMvcTest annotation will load the REST controller class. Otherwise, when we use use @ContextConfiguration, seems ContextConfiguration clear the context REST controller config. We need to add the REST controller to ContextConfiguration such as: @ContextConfiguration(classes = {MockConfig.class, WeatherStationController.class})

Waldenses answered 15/2, 2023 at 19:6 Comment(0)
P
1

Here is a different approach to the controller test that worked for me.

Assumption: The class WeatherStationService is a @SpringBootApplication

Then, the test class below should work for you:

@RunWith(SpringRunner.class)
@SpringApplicationConfiguration(WeatherStationService.class)
@WebIntegrationTest
public class WeatherStationControllerTest {

    @Autowired
    private WebApplicationContext context;

    MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc =  MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void shouldReturnCorrectStation() throws Exception {
        mockMvc.perform(get("/stations")
                .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk();
    }
}

With this test setup, you should no longer need the MockConfig class.

Prow answered 9/1, 2017 at 13:24 Comment(1)
I hope the suggestion will be helpful this time and it is not too late.Prow
E
1

In my case it was about a missing starting slash /

I've appended / to both RequestMapping value and MockHttpServletRequestBuilder post urlTemplate parameters as first character.

Engelhardt answered 3/12, 2019 at 13:3 Comment(0)
E
1

In my case, I have to add the controller also to the @ContextConfiguration.

@ContextConfiguration(classes = {MockConfig.class, WeatherStationController.class})
Encyclopedic answered 1/6, 2023 at 13:52 Comment(0)
D
0

Looking at this answer, I thought the problem might be with using ContextConfiguration to create the proper context.
Also in the documentations for WebMvcTest it says:

Typically @WebMvcTest is used in combination with @MockBean or @Import to create any collaborators required by your @Controller beans.

So I replaced the ContextConfiguration with Import and it worked.

Dichromatism answered 28/12, 2023 at 12:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.