@InjectMocks与Java 6和7的行为不同

时间:2012-05-25 14:08:53

标签: java mockito java-7 type-erasure java-6

使用非常简单的Mockito运行JUnit测试和类我在使用Java 1.6.0_32和Java 1.7.0_04运行测试时看到不同的输出,并且想要了解为什么这种情况正在发生。我怀疑有一些类型的擦除正在进行,但想要一个明确的答案。

以下是我的示例代码和有关如何从命令行运行的说明:

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);
  }
}

编译并运行此示例:

  • 将上述内容保存到文件中
  • 下载并放入同一目录junit.4.10.jarmockito-all-1.9.0.jar
  • 将PATH设置为包含JDK
  • 使用javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
  • 进行编译
  • 使用java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest
  • 运行

我认为上面的输出是null {}因为@InjectMocks字段注入无法正确解析类型,因为它们都是Map类型。 这是正确的吗?

现在更改其中一个模拟名称以匹配类中的字段应该允许Mockito找到匹配项。例如改变

@Mock Map<String, Integer> mockStringInteger;

@Mock Map<String, Integer> stringInteger;

然后用Java 1.6.0_32编译/运行给出(IMHO预期的)输出bar stringInteger,但1.7.0_04给出null stringInteger

以下是我如何运行它(从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

3 个答案:

答案 0 :(得分:5)

  

我相信上面的输出是I null {},因为@InjectMocks字段注入无法正确解析类型,因为它们都是Map类型。这是对的吗?

是的,在这些领域正确Mockito无法消除歧义,所以它只是忽略了这些模棱两可的领域。

  

使用非常简单的Mockito运行JUnit测试和类,当使用Java 1.6.0_32和Java 1.7.0_04运行测试时,我看到不同的输出,并且想要了解为什么会发生这种情况。

实际上,差异在于Arrays.sort的不同行为,因此在JDK 6和JDK 7之间存在Collections.sort()。差异在于新的algorythm应该执行20%以下的交换。这可能是这种交换操作使得JDK6和JDK7下的工作。

如果您只重命名具有相同类型(或相同擦除)的字段的一个模拟字段,那么我可能正在“寻找麻烦”。当模拟无法按类型区分时,你真的应该将所有模拟字段命名为相应的字段,但是Javadoc并没有明确说明。

非常感谢报告这种奇怪的行为,我创建了一个issue on Mockito,但是现在我不会真正解决这个问题,而是确保JDK中的相同行为。解决这种情况可能需要在保持兼容性的同时编写新的算法,同时您应该将所有的场模拟命名为测试类的字段。

目前要做的事情可能是通过额外的比较调整比较器,以在JDK6和JDK7上强制执行相同的顺序。另外在Javadoc中添加一些警告。

编辑:两次通过可能会解决大多数人的问题。

希望有所帮助。感谢您发现问题。


另外,您需要MockitoAnnotations.initMocks(this);或跑步者@RunWith(MockitoJUnitRunner.class),因此不必使用两者,甚至可能会导致一些问题。 :)

答案 1 :(得分:2)

Mockito的行为是未定义的,如果有多个模拟匹配将要注入的一个字段。这里,“匹配”意味着它是正确的类型,忽略任何类型参数 - 类型擦除阻止Mockito知道类型参数。因此,在您的示例中,两个模拟中的任何一个都可以注入两个字段中的任何一个。

您已经设法观察Java 7中Java 6的不同行为这一事实是一个红色的鲱鱼。在任何一个Java版本中,没有理由期望Mockito在mockStringStringmockStringInteger之间正确选择它注入的两个字段中的任何一个。

答案 2 :(得分:0)

这是正确的吗?

事实上,由于type erasure,Mockito无法在运行时/通过反射看到各种地图之间的差异,这使得Mockito很难进行正确的注射。

nullstringString.get("foo")的回复可能有两个原因:

  1. stringString已正确模拟,但未进行存根(get始终返回null)。
  2. stringString未被模拟,因此HashMap仍然没有"foo"的值,因此get将返回null
  3. {}变量的stringInteger响应表示已使用实际(空)HashMap初始化(在课堂中)。

    所以你的输出告诉你的是stringInteger没有被嘲笑。那么stringString呢?

    如果两个@Mock中没有一个名称与被测试类中的任何字段匹配,则不会嘲笑任何内容。原因?我怀疑它无法决定注入哪个字段,所以它不做任何嘲弄。您可以通过显示两个变量来验证这一点,这两个变量都会产生{}。这解释了您的null值。

    如果其中一个@Mock的名称匹配,另一个名称不匹配(您的修改,一个mockedStringString和一个stringInteger,即拥有同名),Mockito应该怎么做?

    你想要做的只是注入其中一个,并且仅在具有相应名称的字段中。在您的情况下,您有mockedStringString(您希望这不匹配)和stringInteger(您希望它匹配)。由于您存在mockedStringString(!),但不匹配,预期结果为null

    换句话说,我认为Java 7的响应是正常的,而Java 6的响应是不行的,对于给出的具体示例。

    要了解您为Java 6获得的(意外)行为发生了什么,请尝试只使用一个@Mock - 如果我正确模拟stringString并且没有 stringInteger的模拟,stringString的模拟被注入stringInteger字段。换句话说,Mockito似乎首先想出它可以注入(给定名称),然后将模拟注入其中一个匹配的可能性(但不一定是正确的)。