Today I learned how to test asynchronous callback with LinkedBlockingQueue by reading TestKeepaliveCallback implementation in AOSP.

The basic idea is to use LinkedBlockingQueue.poll with timeout to wait for a method to be called. A private inner class is designed to capture the details of a method callback. A few expectSomething helper methods are defined to make the test code more readable.

Here is the test class for testing callback to PacketKeepaliveCallback:

private static class TestKeepaliveCallback extends PacketKeepaliveCallback {

    public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };

    private class CallbackValue {
        public CallbackType callbackType;
        public int error;

        public CallbackValue(CallbackType type) {
            this.callbackType = type;
            this.error = PacketKeepalive.SUCCESS;
            assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
        }

        public CallbackValue(CallbackType type, int error) {
            this.callbackType = type;
            this.error = error;
            assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
        }

        @Override
        public boolean equals(Object o) {
            return o instanceof CallbackValue &&
                    this.callbackType == ((CallbackValue) o).callbackType &&
                    this.error == ((CallbackValue) o).error;
        }

        @Override
        public String toString() {
            return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
        }
    }

    private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();

    @Override
    public void onStarted() {
        mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
    }

    @Override
    public void onStopped() {
        mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
    }

    @Override
    public void onError(int error) {
        mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
    }

    private void expectCallback(CallbackValue callbackValue) {
        try {
            assertEquals(
                    callbackValue,
                    mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        } catch (InterruptedException e) {
            fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
        }
    }

    public void expectStarted() {
        expectCallback(new CallbackValue(CallbackType.ON_STARTED));
    }

    public void expectStopped() {
        expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
    }

    public void expectError(int error) {
        expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
    }
}