使用 Jmockit 验证 FutureCallback 效果的最佳方法

Best approach for verifying a FutureCallback effect with Jmockit

在 Jmockit 中验证回调效果的首选机制是什么?

例如,假设我有这个 class。

class ResultGenerator {
    AsyncLauncher asyncLauncher = new AsyncLauncher();

    public void getResultAsync(final ResultSignal resultSignal) {
        asyncLauncher.getResult(new FutureCallback<Result>() {
                @Override
                public void onSuccess(@Nullable Result result) {
                    resultSignal.success(result);
                }

                @Override
                public void onFailure(Throwable t) {
                    resultSignal.failure();
                }
        });
    }
}

在为 ResultGenerator#getResultAsync 编写测试时如何验证 resultSignal.success(result)

例如

@RunWith(JMockit.class)
public class ResultGeneratorTest {
    // Synchronous invocation, mocked AsyncLauncher
    @Test
    public void testGetResultAsync(@Mocked final ResultSignal resultSignal, @Mocked final Result result) throws Exception {
        new MockUp<AsyncLauncher>() {
            @Mock
            void getResult(FutureCallback<Result> futureCallback) {
                futureCallback.onSuccess(result);
            }
        };
        ResultGenerator resultGenerator = new ResultGenerator();
        resultGenerator.getResultAsync(resultSignal);
        new Verifications() {{
            resultSignal.success((Result) any); times = 1;
            resultSignal.failure(); times = 0;
        }};
    }

    // Asynchronous invocation, real AsyncLauncher in use
    @Test
    public void testGetResultAsyncDelayed(@Mocked final Result result) throws Exception {
        final AtomicBoolean latch = new AtomicBoolean(false);
        MockUp<ResultSignal> resultSignalMockUp = new MockUp<ResultSignal>() {
            @Mock(invocations = 1)
            public void success(Result result) {
                latch.set(true);
            }

            @Mock(invocations = 0)
            public void failure() {
                latch.set(true);
            }
        };
        ResultGenerator resultGenerator = new ResultGenerator();
        final ResultSignal resultSignal = resultSignalMockUp.getMockInstance();
        resultGenerator.getResultAsync(resultSignal);
        Awaitility.await().untilTrue(latch);
    }

}

一些注意事项:

  1. ResultGenerator 是您的 SUT(被测系统),您不应该模拟内部结构
  2. ResultSignal是测试合作者,自然要mock出来
  3. 因为您可以验证功能本身,所以单元测试理论中唯一的 "correct" 解决方案是模拟协作者
  4. 您必须确保正确处理超时,否则测试可能永远不会结束

所以一种可能的解决方案是:

@Test
public void getResultAsync_ShouldNotifyResultSignal() throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(1);

    ResultGenerator generator = new ResultGenerator();

    generator.getResultAsync(new MyResultSignal(latch));

    assertTrue(latch.await(1, SECONDS));
}

private static final class MyResultSignal implements ResultSignal {
    private final CountDownLatch latch;

    private MyResultSignal(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void success(Result result) {
        latch.countDown();
    }

    @Override
    public void failure() {}
}