java - JMockit 无法模拟多个类的实例

标签 java unit-testing junit mocking jmockit

我想创建一个 Map,它将 Strings 作为其键,并将 Candidate 类的模拟实例作为其值。

    Map<String, Long> domainNameToId = new HashMap<String, Long>();
    domainNameToId.put("farmaciapuentezurita.es", 1234l);
    domainNameToId.put("vivefarma.com", 2345l);
    domainNameToId.put("eurofarmacia.com", 3456l);

    Map<String, Candidate> expectedCandidates = new HashMap<String, Candidate>();
    for(String domain : domainNameToId.keySet()) {
        final Candidate cand = new MockUp<Candidate>() {
            @Mock Long getDomainId() { return domainNameToId.get(domain); } // private method
            @Mock boolean validateAndPrepare() { return true; }
            @Mock String getRepresentingName() { return domain; }
        }.getMockInstance();
        expectedCandidates.put(domain, cand);
    }

上面的代码在将 JMockit 从 1.20 升级到 1.28 之前可以正常工作

现在我遇到了一个异常:

java.lang.IllegalStateException: Invalid attempt to get uninitialized instance of class com.urlservice.data.Candidate from stateless mockup at ...

我阅读了文档并尝试按以下方式使用 new MockUp(T targetInstance) (这是循环的主体):

final Candidate cand = new Candidate(domain);
new MockUp<Candidate>(cand) {
    @Mock Long getDomainId() { return domainNameToId.get(domain); }  // private method
    @Mock boolean validateAndPrepare() { return true; }
    @Mock String getRepresentingName() { return domain; }
};

结果非常奇怪 - 第一个 Candidate 被正确地模拟了,而其余被模拟的候选者根本没有被模拟,并且调用了他们真正的方法。

我尝试恢复到 Expectations API:

final Candidate cand = new Candidate(domain);
new Expectations(cand) {{
    cand.getDomainId(); result = domainNameToId.get(domain);  // Had to make it public :-(
    cand.validateAndPrepare(); result = true;
    cand.getRepresentingName(); result = domain;
}};

无济于事:

java.lang.IllegalArgumentException: Already mocked: class com.urlservice.data.Candidate at...

我确实想升级到最新版本,但找不到解决此问题的方法。

更新:我没有在 1.28 之前的任何版本中重现此问题,所以我猜这是引入该问题的版本。

此外,与我的第二个示例(new MockUp(T targetInstance))相关,我查看了类 MockUp 第 402 行的源代码,它看起来对我来说就像预期的行为一样,除了第一个实例之外,不模拟任何特定的目标实例:

  MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();

  if (previousMockUp != null) {
     targetType = previousMockUp.targetType;
     mockedClass = previousMockUp.mockedClass;
     return;  // Input param targetInstance is disregarded
  }

我错过了什么?

更新2:我想出了一个失败的测试示例。虽然有点麻烦,但我相信它会明白要点。

public class SampleTest {

class TestedClass {
    private IncrementingDependency dep;
    TestedClass(IncrementingDependency dep) { this.dep = dep; }
    public int getVal() { return dep.inc(); }
}

class IncrementingDependency {
    int val;
    public IncrementingDependency(int val) { this.val = val; }
    public int inc() { return ++val; }
}

@Test
public void sampleTest() {
    List<Integer> inputVals = Arrays.asList(1, 2, 3);
    List<TestedClass> incrementingClasses = new ArrayList<TestedClass>();

    for (Integer num : inputVals) {
        IncrementingDependency dep = new IncrementingDependency(num);
        new MockUp<IncrementingDependency>(dep) {
            @Mock int inc() { return num; }  // Mock with different behavior - DON'T INCREMENT
        };
        incrementingClasses.add(new TestedClass(dep));
    }

    assertThat(incrementingClasses.get(0).getVal()).isEqualTo(1); // Passes - 1 wasn't incremented (mocked behavior)
    assertThat(incrementingClasses.get(1).getVal()).isEqualTo(2); // Fails - real code was called and 2 was incremented to 3
    assertThat(incrementingClasses.get(2).getVal()).isEqualTo(3); // We never get to this point
}
}

请注意,即使这个示例不会失败,我需要在将依赖项传递给 MockUp 的构造函数之前实例化它,这充其量也是有问题的。创建模拟的全部意义不就是不需要实例化它吗?

最佳答案

在仔细研究了 JMockit 的实际项目单元测试(非常清晰且有组织)之后,我设法以一种有点特殊的方式解决了这个问题:

private void expectCandidatesFromMap(final Map<String, Long> domainNameToId) {
    Map<String, Candidate> expectedCandidates = new HashMap<String, Candidate>();

    class MockedCandidate extends MockUp<Candidate> {
        private final String domainName;
        private final Long domainId;

        MockedCandidate(String domainName) {
            this.domainName = domainName;
            this.domainId = domainNameToId.get(domainName);
        }

        @Mock Long getDomainId() { return domainId; }
        @Mock String getRepresentingName() { return domainName; }
        @Mock boolean validateAndPrepare() { return true; }
    }

    for (String domain : domainNameToId.keySet()) {
        expectedCandidates.put(domain, new MockedCandidate(domain).getMockInstance());
    }
}

即使我的测试现在通过了,我也不完全理解为什么模拟类的特定实例的唯一方法是创建另一个临时类(而不是像我之前习惯的那样匿名内联它)此版本)。

这种方法需要多几行(私有(private)字段声明、ctor 实现),但可以说更优雅。

如果 JMockit 的贡献者之一能够对此事做出一些解释,我们将不胜感激。

关于java - JMockit 无法模拟多个类的实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39989269/

相关文章:

java - Java中根据PID杀死一个进程

java - 在 JBOSS 6 AS 上配置 TLS 会导致 ERR_SSL_VERSION_OR_CIPHER_MISMATCH(在 Chrome 中)或 ssl_error_no_cypher_overlap(在 Mozilla 中)错误

angular - 测试 Angular 2 应用程序时如何正确使用 SharedModules 和 Router?

spring - 在测试类弄脏 Spring JUnit 应用程序上下文后如何重置它?

java - Unresolved junit 编译问题

java - 用java编写文本文件

python - 为什么我的测试通过时却出现了相互矛盾的断言?

java - 模拟 DAO 类及其中的方法

java - 如何在 jUnit 中检查文件?

java - 使用来自java程序的参数运行exe文件并将输出发送到文本文件