JMockit 无法模拟 class 的多个实例
JMockit unable to mock more than one instance of class
我想创建一个 Map,它将 Strings 作为其键,并将 class 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)
),我查看了 class 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
的构造函数之前实例化它这一事实充其量也是有问题的。创建 mock 的全部意义不就是不需要实例化它吗?
在仔细检查了 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());
}
}
即使我的测试现在通过了,我还是不完全理解为什么模拟 class 的特定实例的唯一方法是创建另一个临时的 class(而不是匿名内联它,就像我在这个版本之前习惯的那样)。
这种方法需要多几行(私有字段声明、ctor 实现),但可以说更优雅。
如果 JMockit 的贡献者之一可以阐明这个问题,我们将不胜感激。
我想创建一个 Map,它将 Strings 作为其键,并将 class 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)
),我查看了 class 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
的构造函数之前实例化它这一事实充其量也是有问题的。创建 mock 的全部意义不就是不需要实例化它吗?
在仔细检查了 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());
}
}
即使我的测试现在通过了,我还是不完全理解为什么模拟 class 的特定实例的唯一方法是创建另一个临时的 class(而不是匿名内联它,就像我在这个版本之前习惯的那样)。
这种方法需要多几行(私有字段声明、ctor 实现),但可以说更优雅。
如果 JMockit 的贡献者之一可以阐明这个问题,我们将不胜感激。