spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.25k stars 37.98k forks source link

Improve error message when `@Autowired` is missing on JUnit Jupiter test class constructor #33460

Closed michaelisvy closed 2 weeks ago

michaelisvy commented 2 weeks ago

Hi,

let's consider that I forgot to add @Autowired in the below:

@SpringBootTest
public class VisitServiceTest {
    private final VisitService visitService;

    private final ApplicationContext applicationContext;

    //@Autowired
    public VisitServiceTest(VisitService visitService, ApplicationContext applicationContext) {
        this.visitService = visitService;
        this.applicationContext = applicationContext;
    }
}

Since @Autowired has to be explicit in case of constructor injection in a JUnit Jupiter test, I am getting the following error message, which is a bit hard to understand, especially for beginners:

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [com.petclinic.core.VisitService visitService] in constructor [public com.petclinic.core.VisitServiceTest(com.petclinic.core.VisitService,org.springframework.context.ApplicationContext)].

I wonder if it would be possible to catch the exception first before it goes to JUnit, and add something like that:

Did you forget to explicitly add @Autowired above your constructor?

Thanks, and have a great day!

sbrannen commented 2 weeks ago

Hi @michaelisvy,

I wonder if it would be possible to catch the exception first before it goes to JUnit, and add something like that:

No, that's unfortunately not possible.

JUnit Jupiter's internal infrastructure (specifically InterceptingExecutableInvoker.invoke(Constructor<T>, Optional<Object>, ExtensionContext, ExtensionRegistry, ReflectiveInterceptorCall<Constructor<T>, T>)) attempts to resolve parameters for the test class constructor outside any custom interceptor (i.e., a custom InvocationInterceptor.interceptTestClassConstructor(...) implementation).

Consequently, the SpringExtension has no means to intercept the ParameterResolutionException you've encountered.

Since @Autowired has to be explicit in case of constructor injection in a JUnit Jupiter test

That's actually not entirely true. 😉

@TestConstructor can be used to make @Autowired and @Inject unnecessary on a test class constructor. For more information, check out the reference manual.

For example, annotating your test class (or one of its superclasses or implemented interfaces) as follows allows the test to pass without @Autowired on the constructor.

@TestConstructor(autowireMode = ALL)

Similarly, adding the following to your junit-platform.properties or spring.properties file (or setting it via SpringProperties or a JVM system property) also allows your test to pass without @Autowired on the constructor.

spring.test.constructor.autowire.mode = all

In light of the above, I am closing this issue.

Cheers,

Sam

michaelisvy commented 2 weeks ago

thanks a lot @sbrannen for the super-clear response. I learned something new today :)

sbrannen commented 2 weeks ago

thanks a lot @sbrannen for the super-clear response.

You're welcome.

I learned something new today :)

That's always a good thing. 👍

I also learned something new, namely that there's apparently no way for an extension to catch a ParameterResolutionException thrown while resolving arguments for a test class constructor, but I'll likely investigate/discuss that further within the JUnit team.