我正在尝试编写一个单元测试(使用JMockit)来验证根据部分顺序调用方法。具体的用例是确保在事务中调用某些操作,但更一般地说我想验证这样的事情:
beginTransaction
。operation1
到operationN
的方法。endTransaction
。someOtherOperation
在交易之前,期间或之后的某个时间被调用。Expectations and Verifications API似乎无法满足此要求。
如果我有一个@Mocked BusinessObject bo
,我可以用这个来验证是否以任何顺序调用了正确的方法:
new Verifications() {{
bo.beginTransaction();
bo.endTransaction();
bo.operation1();
bo.operation2();
bo.someOtherOperation();
}};
可选择将其设为FullVerifications
以检查没有其他副作用。
要检查排序限制,我可以这样做:
new VerificationsInOrder() {{
bo.beginTransaction();
unverifiedInvocations();
bo.endTransaction();
}};
但这不处理someOtherOperation
案例。我无法将unverifiedInvocations
替换为bo.operation1(); bo.operation2()
,因为这会在调用中添加总顺序。正确实施业务方法可以调用bo.operation2(); bo.operation1()
。
如果我成功:
new VerificationsInOrder() {{
unverifiedInvocations();
bo.beginTransaction();
unverifiedInvocations();
bo.endTransaction();
unverifiedInvocations();
}};
然后在事务之前调用someOtherOperation
时,我得到“没有未验证的调用”。尝试bo.someOtherOperation(); minTimes = 0
也不起作用。
那么:有没有一种干净的方法来使用JMockIt中的Expectations / Verifications API指定方法调用的部分排序要求?或者我是否必须使用MockClass
并手动跟踪调用, a la :
@MockClass(realClass = BusinessObject.class)
public class MockBO {
private boolean op1Called = false;
private boolean op2Called = false;
private boolean beginCalled = false;
@Mock(invocations = 1)
public void operation1() {
op1Called = true;
}
@Mock(invocations = 1)
public void operation2() {
op2Called = true;
}
@Mock(invocations = 1)
public void someOtherOperation() {}
@Mock(invocations = 1)
public void beginTransaction() {
assertFalse(op1Called);
assertFalse(op2Called);
beginCalled = true;
}
@Mock(invocations = 1)
public void endTransaction() {
assertTrue(beginCalled);
assertTrue(op1Called);
assertTrue(op2Called);
}
}
答案 0 :(得分:1)
从我对 jmockit 的使用来看,我相信即使在最新版本 1.49 中,答案也是否定的。
您可以使用带有一些内部字段的 MockUp
扩展来实现此类高级验证,以跟踪调用哪些函数、何时调用以及以何种顺序调用。
例如,我实现了一个简单的 MockUp
来跟踪方法调用计数。此示例的目的是真实的,因为 Verifications
和 Expectations
times
字段在模拟 ThreadGroup
时不起作用(也可用于其他敏感类型):< /p>
public class CalledCheckMockUp<T> extends MockUp<T>
{
private Map<String, Boolean> calledMap = Maps.newHashMap();
private Map<String, AtomicInteger> calledCountMap = Maps.newHashMap();
public void markAsCalled(String methodCalled)
{
if (methodCalled == null)
{
Log.logWarning("Caller attempted to mark a method string" +
" that is null as called, this is surely" +
" either a logic error or an unhandled edge" +
" case.");
}
else
{
calledMap.put(methodCalled, Boolean.TRUE);
calledCountMap.putIfAbsent(methodCalled, new AtomicInteger()).
incrementAndGet();
}
}
public int methodCallCount(String method)
{
return calledCountMap.putIfAbsent(method, new AtomicInteger()).get();
}
public boolean wasMethodCalled(String method)
{
if (method == null)
{
Log.logWarning("Caller attempted to mark a method string" +
" that is null as called, this is surely" +
" either a logic error or an unhandled edge" +
" case.");
return false;
}
return calledMap.containsKey(method) ? calledMap.get(method) :
Boolean.FALSE;
}
}
使用如下所示,其中 cut1 是包装实际 ThreadGroup
的动态代理类型:
String methodId = "activeCount";
CalledCheckMockUp<ThreadGroup> calledChecker = new CalledCheckMockUp<ThreadGroup>()
{
@Mock
public int activeCount()
{
markAsCalled(methodId);
return active;
}
};
. . .
int callCount = 0;
int activeCount = cut1.activeCount();
callCount += 1;
Assertions.assertTrue(calledChecker.wasMethodCalled(methodId));
Assertions.assertEquals(callCount, calledChecker.methodCallCount(methodId));
我知道问题很老,这个例子不完全适合 OP 的用例,但希望它可以帮助指导其他人找到一个潜在的解决方案(或 OP,天佑,这仍然没有解决一个重要的用途情况,这不太可能)。
鉴于 OP 尝试执行的操作的复杂性,在您的自定义 $advice
中覆盖 MockUp
方法可能有助于简化区分和记录方法调用。此处的文档:Applying AOP-style advice。
答案 1 :(得分:0)
如果你真的需要这样的测试那么:不要使用模拟库但是创建你自己的模拟内部状态可以简单地检查方法的正确顺序。 但测试调用顺序通常是一个不好的迹象。我的建议是:不要测试它,重构。你应该测试你的逻辑和结果,而不是一系列的调用。检查副作用是否正确(数据库内容,服务交互等)。如果你测试序列,那么你的测试基本上是你的生产代码的精确副本。那么这种测试的附加价值是什么?而这样的测试也非常脆弱(任何重复)。
也许你应该让你的代码看起来像那样:
beginTransaction()
doTransactionalStuff()
endTransaction()
doNonTransactionalStuff()