'Spring Boot - Test for controller fails with 404 code
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?
Solution 1:[1]
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 :
Solution 2:[2]
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.
Solution 3:[3]
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());
}
}
Solution 4:[4]
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)
Solution 5:[5]
I import external configuration class by @ContextConfiguration(classes = MyConfig.class)
When I changed in MyConfig
annotation @Configuration
into @TestConfiguration
it started to work properly.
Solution 6:[6]
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.
Solution 7:[7]
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 {
Solution 8:[8]
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.
Solution 9:[9]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | O.Badr |
Solution 2 | rougou |
Solution 3 | Patrick |
Solution 4 | catch23 |
Solution 5 | TOUDIdel |
Solution 6 | |
Solution 7 | |
Solution 8 | |
Solution 9 | Ismail Yavuz |