Are the rules/behaviors around @Autowired
different when writing tests? It seems that with a test, you can autowire to a concrete type, but if you try the same thing inside a @Component
it will fail. This is a contrived example, but it's something I ran into and am just trying to understand better.
Contrived example code:
public interface Gizmo {
void whirr();
}
@Configuration
public class GizmoConfiguration {
@Bean
@Profile("no-dependencies")
public Gizmo fooGizmoBean() {
return new FooGizmo();
}
@Bean
@Profile("!no-dependencies")
public Gizmo barGizmoBean() {
return new BarGizmo();
}
public class FooGizmo implements Gizmo {
@Override
public void whirr() {
}
}
public class BarGizmo implements Gizmo {
@Override
public void whirr() {
}
}
}
Test that runs fine:
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(Application.Profiles.NO_DEPENDENCIES)
public class TestClass {
@Autowired
private GizmoConfiguration.FooGizmo gizmo;
@Test
public void test() {
assertNotNull(gizmo);
}
}
Component that causes java.lang.IllegalStateException: Failed to load ApplicationContext
:
@Component
public class TestComponent {
@Autowired
private GizmoConfiguration.FooGizmo gizmo;
}
because of:
No qualifying bean of type 'GizmoConfiguration$FooGizmo' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Are the rules/behaviors around @Autowired different when writing tests?
Not exactly: the rules are actually exactly the same. The difference is in terms of timing with regard to how Spring determines if a given bean is an autowire candidate.
It seems that with a test, you can autowire to a concrete type, but if you try the same thing inside a @Component it will fail.
I understand why you would think that, since your example demonstrates that behavior, but your analysis is not exactly correct.
So let me explain...
When Spring attempts to perform autowiring for your @Component
class, the only information it has about types (i.e., classes and interfaces) for beans coming from @Bean
methods is the information available in an @Bean
method's formal signature.
In your example, when Spring searches for so-called "autowire candidates" to inject into your @Component
, Spring only sees a bean of type Gizmo
for your fooGizmoBean()
@Bean
method. So that's why you see the "No qualifying bean of type 'GizmoConfiguration$FooGizmo'" error, which happens to be completely correct.
If you want Spring to be able to autowire your @Component
using the concrete type, you will have to redefine the signature of your fooGizmoBean()
@Bean
method to return FooGizmo
instead of Gizmo
.
So, that's the first half of the story. The second half of the story is why the Spring TestContext Framework is able to perform autowiring by the concrete type for the test instance.
The reason that works is that the ApplicationContext
has already been completely started (i.e., all beans have been instantiated and all @Bean
methods have been invoked by the container) by the time the testing framework attempts to perform dependency injection. By that point in time, the fooGizmoBean()
method has already been invoked by Spring, and Spring now knows the concrete type is actually a FooGizmo
. Thus, @Autowired FooGizmo gizmo;
works in the test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With