Spring Boot version: 3.2.3, 3.2.4 (Spring Core 6.1.4)
Java version: Temurin 17 (17.0.10)
Reproduced on Windows and Github Actions with Ubuntu 22.04.
Minimal example: https://github.com/RHarryH/spring-webmvc-github-issue
Description:
I have observed weird issue of WebMvcTest failure with code 405 instead of expected 200 because Spring does not resolve controller method based on the request url. 405 error happens when there are two endpoints with the same request url but different HTTP method. When request urls are different 404 error is thrown.
This happens only when specific hierarchy of controllers is used and when WebMvcTest is run after SpringBootTest (achieved by changing test class execution order in junit-platform.properties.
The hierarchy of the controllers is as follows:
Controller interface defining endpoints and annotating them with @XMapping annotations
AbstractController implementing delete method. Please not it is a package-private abstract class
ActualController implementing remaining methods
The presence of AbstractController is the main cause of the issue. Working workaround is making it public.
When debugging tests SpringBootTest logs contains:
2024-04-07T12:01:20.781+02:00 DEBUG 33568 --- [ main] _.s.web.servlet.HandlerMapping.Mappings :
c.a.i.ActualController:
{POST [/v1/a]}: add(Body,BindingResult)
{POST [/v1/a/{id}]}: update(UUID,Body,BindingResult)
{DELETE [/v1/a/{id}]}: delete(UUID)
while WebMvcTest logs miss DELETE method:
2024-04-07T12:01:22.203+02:00 DEBUG 33568 --- [ main] _.s.web.servlet.HandlerMapping.Mappings :
c.a.i.ActualController:
{POST [/v1/a]}: add(Body,BindingResult)
{POST [/v1/a/{id}]}: update(UUID,Body,BindingResult)
I have tracked down the rootcause to the org.springframework.core.MethodIntrocpector class and selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) method (
).
Line 74 correctly inspects the method. The problem is in line 77. When SpringBootTest tests are run the fields looks like below:
method = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
specificMethod = {Method@7493} "public void com.avispa.issue.ActualController.delete(java.util.UUID)"
result = {RequestMappingInfo@7494} "{DELETE [/v1/a/{id}]}"
bridgedMethod = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
But then whenWebMvcTest tests are run it looks like below:
method = {Method@9155} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
specificMethod = {Method@9156} "public void com.avispa.issue.ActualController.delete(java.util.UUID)"
result = {RequestMappingInfo@9157} "{DELETE [/v1/a/{id}]}"
bridgedMethod = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
As you can see in second case method and bridgedMethod represents the same method but are in fact different instances of Method class. And because the comparison in line 77 is done by reference, it failes and does not add found DELETE method to the mappings registry.
When SpringBootTest tests are disabled, the problem does not exist.
Spring Boot version: 3.2.3, 3.2.4 (Spring Core 6.1.4)
Java version: Temurin 17 (17.0.10)
Reproduced on Windows and Github Actions with Ubuntu 22.04.
Minimal example: https://github.com/RHarryH/spring-webmvc-github-issue
Description:
I have observed weird issue of
WebMvcTestfailure with code405instead of expected200because Spring does not resolve controller method based on the request url.405error happens when there are two endpoints with the same request url but different HTTP method. When request urls are different404error is thrown.This happens only when specific hierarchy of controllers is used and when
WebMvcTestis run afterSpringBootTest(achieved by changing test class execution order injunit-platform.properties.The hierarchy of the controllers is as follows:
Controllerinterface defining endpoints and annotating them with@XMappingannotationsAbstractControllerimplementingdeletemethod. Please not it is a package-private abstract classActualControllerimplementing remaining methodsThe presence of
AbstractControlleris the main cause of the issue. Working workaround is making itpublic.When debugging tests
SpringBootTestlogs contains:while
WebMvcTestlogs missDELETEmethod:I have tracked down the rootcause to the
org.springframework.core.MethodIntrocpectorclass andselectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup)method (spring-framework/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
Line 75 in 9bd6aef
Line 74 correctly inspects the method. The problem is in line 77. When
SpringBootTesttests are run the fields looks like below:But then when
WebMvcTesttests are run it looks like below:As you can see in second case
methodandbridgedMethodrepresents the same method but are in fact different instances ofMethodclass. And because the comparison in line 77 is done by reference, it failes and does not add foundDELETEmethod to the mappings registry.When
SpringBootTesttests are disabled, the problem does not exist.