7

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.

fashuser
  • 2,152
  • 3
  • 29
  • 51

2 Answers2

2

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?

emanciperingsivraren
  • 1,215
  • 2
  • 15
  • 27
0

You don't need to do it any more. Mockito itself starting from version 1.8.3 now support annotated mocks and mock injections as described here: http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/Mockito.html#21

We do now the following for our unit tests:

// No annotation required
public class SomeTest {

  @Mock
  private SomeDependency someDependency;
  @Mock
  private SomeDependency2 someDependency2;
  @InjectMocks
  private ClassUnderTest classUnderTest;

  @BeforeMethod(alwaysRun = true)
  public void setUp() {
    MockitoAnnotations.initMocks(this);
  }

  public void testSomething() {
    // Do your Mockito test here.
  }
}
Artem
  • 7,275
  • 15
  • 57
  • 97