【发布时间】:2016-10-12 01:44:40
【问题描述】:
我想创建一个 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:从无状态模型中获取 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:已经模拟: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 的构造函数之前实例化我的依赖项这一事实充其量是有问题的。创建一个模拟的全部意义不是你不需要实例化它吗?
【问题讨论】:
-
使用
new MockUp<Candidate>(cand)时无法重现“奇怪”的结果。它似乎适用于 1.28。你能展示一个失败的示例测试吗? -
请注意 API documentation for
Mock<T>(T)表示“仅影响给定实例”。因此,在Candidate的任何其他 实例上调用的方法都不会转到@Mock方法。 -
@Rogério 当然,这正是我想要实现的。我会尽快提供一个失败的示例测试,但我最后放置的 JMockit 代码示例是否强烈支持我的主张?
-
失败的示例测试在 JMockit 1.20 中也失败了;这部分从 1.20 到 1.28 没有任何变化。另外,我不明白测试的重点:为什么要模拟
IncrementingDependency?为什么要“模仿不同的行为”? -
请注意模拟(和假货)的全部意义一般是它们“模仿”或“模仿”实际(预期)行为模拟/伪造的依赖项,但没有执行实际的实现。您不会将模拟用于不同的行为。
标签: java unit-testing junit mocking jmockit