I am using springockito-annotations 1.0.9
for integration testing.
I have the following controller:
@Autowired
public Controller(
@Qualifier("passwordService ") PasswordService passwordService ,
@Qualifier("validator") Validator validator,
@Qualifier("reportService") ReportService reportService,
DateCalculator dateCalculator,
Accessor accessor){
this.passwordService = passwordService;
this.validator = validator;
this.reportService = reportService;
this.dateCalculator = dateCalculator;
this.accessor = accessor;
}
In the test I am going to replace beans from context using @ReplaceWithMock annotation.
But unfortunatly it works only for dependencies whithout @Qualifier annotation.
Namely, my test looks like this:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class, classes = {TestContext.class})
public class ControllerTest {
@Autowired
@ReplaceWithMock
private PasswordService passwordService ;
@Autowired
@ReplaceWithMock
private Validator validator;
@Autowired
@ReplaceWithMock
private ReportService reportService;
@Autowired
@ReplaceWithMock
private DateCalculator dateCalculator;
@Autowired
@ReplaceWithMock
private Accessor accessor;
@Autowired
private Controller controller;
}
In the last case after initializing context only DateCalculator and Accessor beans replacing correctly with needed mocks, but the another bean autowiring as normal beans from main context.
After debugging I have found that QualifierAnnotationAutowireCandidateResolver couldn't identify correctly bean. In the lines below beginning from 229:
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
Spring tried to extract qualifier from mocked dependency, but it is empty.
Will be good to know how I can correctly replace dependency with @Qualifier to mock object.
We can use @Qualifier and @Primary for the same bean. Use @Qualifier to inject specific bean otherwise Spring injects bean by default which is annotated with @Primary.
NOTE: if you are creating bean with @Bean, it will be injected byType if there is duplicates then it will injected byName. we no need to mention @Bean(name="bmwDriver") . so you can directly use qualifier("bmwDriver") wherever you need in classes.
The @Qualifier annotation can be used on any class annotated with @Component or on methods annotated with @Bean . This annotation can also be applied on constructor arguments or method parameters. Injecting Bike bean in VehicleService using @Autowired with @Qualifier annotation.
It's worth noting that if both the @Qualifier and @Primary annotations are present, then the @Qualifier annotation will have precedence. Basically, @Primary defines a default, while @Qualifier is very specific.
Edit: added a link to alternatives to whitebox, it disappears in later versions of Mockito
It is possible to use mockitos @Mock
and @InjectMocks
to inject things into your class that you want to test, as suggested in an other post. I used to think it was a great way to test spring managed beans, but now i think it is problematic; if the
injection done by @InjectMocks
fails, it does so silently, and you don't know why. When creating the test it can be manageble, but when you have some tests like this, and several tests starts to fail with nullpointers because of a small unintentional
change to the application context that someone made, or after a merge that introduced a minor anomaly, or similar, it gets more confusing than it need to be.
I advise you to use mockitos Whitebox
instead, see my example below. With it you can explicitly tell what field you want to "inject" with what object, and in complicated situations, you can inject into more then one object. Mockito uses Whitebox when injecting (but swallows all exceptions).
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration( /*something that fits your setup*/ )
public class ControllerTest {
@Autowired
private PasswordService passwordService ;
@Autowired
private Validator validator;
@Autowired
private ReportService reportService;
@Autowired
private Controller testObject;
@Before
public void setupBefore() {
//Since this "injection" is done manually the qualifiers does not matter
Whitebox.setInternalState(testObject, "passwordService", passwordService);
Whitebox.setInternalState(testObject, "validator", validator);
Whitebox.setInternalState(testObject, "reportService", reportService);
}
@Test
public void testSomething() {
}
}
If you are doing regular unit testing Whitebox
can help to do testing without a spring context. I can highly recommend that approach (but it is a bit off topic, and I will not post the example I wrote before I noticed that you were doing integration tests ;) ).
Edit: If you are using later versions of Mockito you will notice that Whitebox has disappeared, so what to do instead? I faced that situation, and asked for advice: What do I use instead of Whitebox in Mockito 2.2 to set fields?
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