JMockit无法模拟多个类

时间:2016-10-12 01:44:41

标签: java unit-testing junit mocking jmockit

我想创建一个Map,它以字符串为键并模拟类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:无效尝试从无状态模型获取类com.urlservice.data.Candidate的未初始化实例       在......

我阅读了文档并试图以下列方式使用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; }
};

结果非常奇怪 - 第一个候选人被嘲笑得恰到好处,而其他被嘲笑的候选人根本没有被嘲笑,他们真正的方法被召唤。

我尝试恢复到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:已经模拟过:class com.urlservice.data.Candidate       在...

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

更新:我没有设法在任何版本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
  }

我错过了什么?

UPDATE2:我想出了一个失败的测试示例。这有点麻烦,但我相信它会明白这一点。

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的构造函数之前实例化我的依赖性这一事实充其量也是有问题的。创建模拟的全部意义是,您不需要实例化它吗?

1 个答案:

答案 0 :(得分:0)

在仔细研究了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());
    }
}

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

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

如果JMockit的一位贡献者可以对此事做出一些启示,我们将非常感激。