Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@InjectMocks behaving differently with Java 6 and 7

With a very simple Mockito run JUnit test and class I am seeing different output when the test is run with Java 1.6.0_32 and Java 1.7.0_04 and want to understand why this is happening. I suspect there is some type erasure going on but would like a definitive answer.

Here is my example code and instructions on how to run from the command line:

FooServiceTest.java

import org.junit.*;
import org.junit.runner.*;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
import java.util.*;

@RunWith(MockitoJUnitRunner.class)
public class FooServiceTest {
  @Mock Map<String, String> mockStringString;
  @Mock Map<String, Integer> mockStringInteger;

  @InjectMocks FooService fooService;

  public static void main(String[] args) {
    new JUnitCore().run(FooServiceTest.class);
  }

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void checkInjection() {
    when(mockStringString.get("foo")).thenReturn("bar");
    fooService.println();
  }
}

FooService.java

import java.util.*;

public class FooService {
  private Map<String, String> stringString = new HashMap<String, String>();
  private Map<String, Integer> stringInteger = new HashMap<String, Integer>();

  public void println() {
    System.out.println(stringString.get("foo") + " " + stringInteger);
  }
}

To compile and run this example:

  • save the above into files
  • download and put in the same directory junit.4.10.jar and mockito-all-1.9.0.jar
  • set PATH to include a JDK
  • compile with javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
  • run with java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest

I believe the output from above is null {} because @InjectMocks field injection cannot correctly resolve the types since they are both of type Map. Is this correct?

Now changing one of the mock names to match the field in the class should allow Mockito to find a match. For example changing

@Mock Map<String, Integer> mockStringInteger;

to

@Mock Map<String, Integer> stringInteger;

then compiling/running with Java 1.6.0_32 gives (IMHO the expected) output bar stringInteger but with 1.7.0_04 gives null stringInteger.

Here is how I am running it (from a command line in Windows 7):

E:\src\mockito-test>set PATH="C:\Program Files (x86)\Java\jdk1.6.0_32\bin"
E:\src\mockito-test>javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
E:\src\mockito-test>java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest
    bar stringInteger
E:\src\mockito-test>set PATH="C:\Program Files (x86)\Java\jdk1.7.0_04\bin"
E:\src\mockito-test>javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
E:\src\mockito-test>java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest
    null stringInteger
like image 575
andyb Avatar asked May 25 '12 14:05

andyb


2 Answers

I believe the output from above is I null {} because @InjectMocks field injection cannot correctly resolve the types since they are both of type Map. Is this correct?

Yes, correct on these field Mockito cannot clear the ambiguity, so it simply ignores these ambiguous fields.

With a very simple Mockito run JUnit test and class I am seeing different output when the test is run with Java 1.6.0_32 and Java 1.7.0_04 and want to understand why this is happening.

Actually the diferrence resides in a different behavior of Arrays.sort and hence Collections.sort() between JDK 6 and JDK 7. The difference resides in the new algorythm that should perform 20% less swaps. That's probably this swap operation that made things work under JDK6 and JDK7.

If I may you are "looking for trouble" if you rename only one mock field of the fields that have the same type (or same erasure). When mocks cannot be differentiated by type you really should name all your mock field to corresponding field, but the Javadoc does not state that clearly.

Thanks a lot for reporting that odd behavior by the way, I created an issue on Mockito, however for now I won't really solve this issue, but rather ensure the same behavior across JDKs. Solving this situation might need to code a new algorythm while maintaining compatibility, in the mean time you should name all your field mocks accordingly to the fields of tested class.

For now the thing to do will probably be tweaking the comparator with additional comparisons to enforce the same order on JDK6 and JDK7. Plus adding some warning in the Javadoc.

EDIT : Making two passes might solve the problem for most people.

Hope that helps. Thx for spotting the issue.


Also by the way you need either MockitoAnnotations.initMocks(this); or the runner @RunWith(MockitoJUnitRunner.class), using both is not necessary, and might even cause some problems. :)

like image 118
Brice Avatar answered Oct 27 '22 12:10

Brice


Mockito's behaviour is undefined, if there's more than one mock that matches one of the fields that is going to be injected. Here, "matches" means it's the right type, ignoring any type parameters - type erasure prevents Mockito from knowing about the type parameters. So in your example, either of the two mocks could be injected into either of the two fields.

The fact that you've managed to observe different behaviour with Java 6 from Java 7 is a bit of a red herring. There is no reason, in either version of Java, to expect Mockito to choose correctly between mockStringString or mockStringInteger, for either one of the two fields that it's injecting.

like image 26
Dawood ibn Kareem Avatar answered Oct 27 '22 11:10

Dawood ibn Kareem