Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ReplaceWithMock with @Qualifier

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.

like image 457
fashuser Avatar asked Sep 25 '14 07:09

fashuser


People also ask

Can we use @primary and @qualifier together?

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.

Can Bean and qualifier be used together?

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.

Which annotation do you use @qualifier with?

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.

What is difference between @primary and @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.


1 Answers

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?

like image 190
emanciperingsivraren Avatar answered Oct 05 '22 04:10

emanciperingsivraren