Tuesday 1 April 2014

Mockito - don't use @InjectMocks if your constructor will be calling methods on your injected mocks

I had this:

@Mock
MessageSource mockMessageSource;

@InjectMocks
PhoneNumberUtil phoneNumberUtil;


@Before
public void setup() {

    MockitoAnnotations.initMocks(this);


    when(mockMessageSource.getMessage(eq("first.key"), (Object[]) eq(null), anyString(),eq(Locale.getDefault()))).thenReturn("This is a default message");
    when(mockMessageSource.getMessage(eq("second.key"), (Object[]) eq(null), anyString(),eq(Locale.getDefault()))).thenReturn("second Value");


}


Where


public PhoneNumberUtil(MessageSource messageSource) {
    this.firstValue = messageSource.getMessage("first.key"), null, FIRST_DEFAULT_VALUE, Locale.getDefault());
    this.secondValue = messageSource.getMessage("second.key"), null, SECOND_DEFAULT_VALUE, Locale.getDefault());
}



I was running my unit test but wondering why the fields "firstValue" and "secondValue" were still coming out as null.

I already mocked the messageSource.getMessage() calls. Why weren't my mocks getting called??

Then I realised: the when().thenReturn() calls weren't getting called until AFTER PhoneNumberUtil was already initialized!


I was using @InjectMocks and once the line

MockitoAnnotations.initMocks(this);

was called, phoneNumberUtil would already get initialised with mockMessageSource. But at that point, mockMessageSource isn't setup to do anything yet.

So the fix was to do this in my setup()



@Mock
MessageSource mockMessageSource;

PhoneNumberUtil phoneNumberUtil;

@Before
public void setup() {

    MockitoAnnotations.initMocks(this);


    when(mockMessageSource.getMessage(eq("first.key"), (Object[]) eq(null), anyString(),eq(Locale.getDefault()))).thenReturn("This is a default message");
    when(mockMessageSource.getMessage(eq("second.key"), (Object[]) eq(null), anyString(),eq(Locale.getDefault()))).thenReturn("second Value");

    phoneNumberUtil = new PhoneNumberUtil(mockMessageSource);

}


If you need to make a call on a mocked dependency in the constructor of the class you're testing, then you can't use the @InjectMocks pattern.

You need to create the class you are testing using code, not annotations, using constructor injection to pass in the mocked dependencies, and you need to create it after you've mocked the methods being called in the constructor.