11

通过一个非常简单的 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 编译/运行会给出(恕我直言是预期的)输出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
4

3 回答 3

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()。不同之处在于新的算法应该减少 20% 的交换。这可能是这种交换操作使事情在 JDK6 和 JDK7 下工作。

如果您只重命名具有相同类型(或相同擦除)的字段的一个模拟字段,我可能会“自找麻烦”。当不能按类型区分模拟时,您确实应该将所有模拟字段命名为相应的字段,但 Javadoc 并没有明确说明。

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

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

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

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


另外顺便说一下,您需要 anyMockitoAnnotations.initMocks(this);或 runner @RunWith(MockitoJUnitRunner.class),两者都不是必需的,甚至可能会导致一些问题。:)

于 2012-06-29T19:30:27.863 回答
2

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

您已经设法观察到 Java 6 与 Java 7 的不同行为这一事实有点牵强附会。在任何一个 Java 版本中,都没有理由期望 Mockito 正确地在它注入的两个字段中的任何一个之间选择mockStringStringor 。mockStringInteger

于 2012-05-26T23:10:52.533 回答
0

这是正确的吗?

事实上,由于类型擦除,Mockito 在运行时/通过反射无法看到各种 Map 之间的差异,这让 Mockito 很难进行正确的注入。

null响应可能有stringString.get("foo")两个原因:

  1. stringString 正确模拟,但没有发生存根(get总是返回null)。
  2. stringString 没有被嘲笑,所以仍然是HashMap没有值的"foo",所以get会返回null

对变量的{}响应stringInteger意味着它已被初始化(在类中)为实际的 (empty) HashMap

所以你的输出告诉你的是,stringInteger它没有被嘲笑。怎么样stringString

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

如果其中一个@Mocks 有一个匹配的名称,而另一个有一个不匹配的名称(您的修改, onemockedStringString和 one stringInteger,即具有相同的名称),Mockito 应该怎么做?

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

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

要查看 Java 6 的(意外)行为发生了什么,请尝试只使用一个@Mock- 如果我正确地模拟stringString并且没有模拟 for stringInteger,则模拟 forstringString被注入到该stringInteger字段中。换句话说,Mockito 似乎首先弄清楚它可以注入(给定名称),然后将模拟注入到匹配的可能性之一(但不一定是正确的那个)。

于 2012-05-26T09:49:58.817 回答