开发者

Verifying partially ordered method invocations in JMockit

I'm trying to write a unit test (using JMockit) that verifies that methods are called according to a partial order. The specific use case is ensuring that certain operations are called inside a transaction, but more generally I want to verify something like this:

  • Method beginTransaction is called.
  • Methods operation1 through to operationN are called in any order.
  • Method endTransaction is called.
  • Method someOtherOperation is called some time before, during or after the transaction.

The Expectations and Verifications APIs don't seem to be able to handle this requirement.

If I have a @Mocked BusinessObject bo I can verify that the right methods are called (in any order) with this:

new Verifications() {{
    bo.beginTransaction();
    bo.endTransaction();
    bo.operation1();
    bo.operation2();
    bo.someOtherOperation();
}};

optionally making it a FullVerifications to check that there are no other side-effects.

To check the ordering constraints I can do something like this:

new VerificationsInOrder() {{
    bo.beginTransaction();
    unverifiedInvocations();
    bo.endTransaction();
}};

but this does not handle the someOtherOperation case. I can't replace the unverifiedInvocations with bo.operation1(); bo.operation2() because that puts a total ordering on the invocations. A correct implementation of the business method could call bo.operation2(); bo.operation1().

If I make开发者_如何转开发 it:

new VerificationsInOrder() {{
    unverifiedInvocations();
    bo.beginTransaction();
    unverifiedInvocations();
    bo.endTransaction();
    unverifiedInvocations();
}};

then I get a "No unverified invocations left" failure when someOtherOperation is called before the transaction. Trying bo.someOtherOperation(); minTimes = 0 also doesn't work.

So: Is there a clean way to specify partial ordering requirements on method calls using the Expectations/Verifications API in JMockIt? Or do I have to use a MockClass and manually keep track of invocations, 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);
    }
}


if you really need such test then: don't use mocking library but create your own mock with state inside that can simply check the correct order of methods. but testing order of invocations is usually a bad sign. my advice would be: don't test it, refactor. you should test your logic and results rather than a sequence of invocations. check if side effects are correct (database content, services interaction etc). if you test the sequence then your test is basically exact copy of your production code. so what's the added value of such test? and such test is also very fragile (as any duplication).

maybe you should make your code looks like that:

beginTransaction()
doTransactionalStuff()
endTransaction()
doNonTransactionalStuff()


From my usage of jmockit, I believe the answer is no even in the latest version 1.49.

You can implement this type of advanced verification using a MockUp extension with some internal fields to keep track of which functions get called, when, and in what order.

For example, I implemented a simple MockUp to track method call counts. The purpose of this example is real, for where the Verifications and Expectations times fields did not work when mocking a ThreadGroup (useful for other sensitive types as well):

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;
    }
}

With usage like the following, where cut1 is a dynamic proxy type that wraps an actual 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));

I know question is old and this example doesn't fit OP's use case exactly, but hoping it may help guide others to a potential solution that come looking (or the OP, god-forbid this is still unsolved for an important use case, which is unlikely).

Given the complexity of what OP is trying to do, it may help to override the $advice method in your custom MockUp to ease differentiating and recording method calls. Docs here: Applying AOP-style advice.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜