Java unit testing: the easiest way to test if a callback is invoked

14,275

Solution 1

This is typically what a mocking framework can do for you.

With Mockito for instance:

// imports ommited for brevity
@Test
public void callbackIsCalled()
{
    final CallBack callBack = mock(CallBack.class);
    method(callBack);

    verify(callBack, only()).handle(any());
}

Of course, this is an example of verification mode (only()) and value matcher (any()). You can do more...

(other mocking frameworks exist, but I personally find Mockito the easiest to use, in addition to being one of the most powerful)

Solution 2

Given that this is the sort of thing you're likely to need in several places, I would just create a named class to use for tests:

public class FakeCallback implements Callback {
    private boolean wasSuccessful;
    private boolean handleCalled;

    @Override public void handle(boolean isSuccessful) {
        this.wasSuccessful = isSuccessful;
        handleCalled = true;
    }

    // Getters for fields above
}

You can then use something like:

// Arrange...
FakeCallback callback = new FakeCallback();

// Act...
method(callback);

// Assert
assertTrue(callback.wasHandleCalled());
assertTrue(callback.wasSuccessful());

You could absolutely use a mocking framework for this instead, but personally I find that often it's simpler to create a single fake implementation than set up mocks repeatedly. Both ways will work though.

Solution 3

Give list::add as callback

When the task is to test a callback that is a functional interface accepting one parameter (here a boolean, could as well be String or any random type), it seems most concise to prepare a list, pass the List.add(e) method as callback and then check the content of the list:

List<Boolean> callbackArgs = new ArrayList<>();
methodUnderTest(callbackArgs::add);
// assert that the callback was called exactly once and with a "true" value: 
assertEquals(Arrays.asList(true), callbackArgs);

Alternative case for a callback that accepts Strings:

List<String> callbackArgs = new ArrayList<>();
methodUnderTest(callbackArgs::add);
// assert that the callback was called twice with "foo" and "bar" values respectively: 
assertEquals(Arrays.asList("foo", "bar"), callbackArgs);

Analogously, a counter class may serve for testing a callback that accepts no parameter. Here using AtomicInteger, as that seems to be the only counter-like class available in the standard libs - the atomicity property is not needed here:

AtomicInteger callbackCounter = new AtomicInteger();
methodUnderTest(callbackCounter::incrementAndGet);
// assert that the callback was called 5 times: 
assertEquals(5, callbackCounter.get());
Share:
14,275
Lyubomyr Shaydariv
Author by

Lyubomyr Shaydariv

... and I fail at interviews.

Updated on June 14, 2022

Comments

  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv about 2 years

    I often work with methods that accept callbacks, and callbacks seem to be somewhat hard to test. Let's consider the following scenario, if there's a method that accepts a callback with a single method (for simplicity, I assume the testing method is synchronous), the following boilerplate could be written just to ensure that a callback method is invoked:

    @Test
    public void testMethod() {
        final boolean[] passed = {false};
        method(new Callback() {
            @Override
            public void handle(boolean isSuccessful) {
                passed[0] = isSuccessful;
            }
        });
        assertTrue(passed[0]);
    }
    

    It looks like a surrogate. I would like to know: is there a more elegant way to test such code to make the code above look more like the pseudo-code below?

    @Test
    public void testMethod() {
        // nothing explicit here, implicit boolean state provided by a test-runner
        method(new Callback() {
            @Override
            public void handle(boolean isSuccessful) {
                if ( isSuccessful ) {
                    pass(); // not sure how it should look like:
                            // * an inherited method that sets the state to "true"
                            // * or an object with the pass method
                            // * whatever
                            // but doesn't exit testMethod(), just sets the state
                }
            }
        });
        // nothing explicit here too:
        // test runner might check if the state is changed to true
        // otherwise an AssertionError might be thrown at the end of the method implicitly
    }
    

    A little cleaner. Is it possible in JUnit, TestNG or any other testing framework? Thanks!


    UPDATE

    Sorry, I seem to have asked a vague question that doesn't really meets what I wanted to ask. I basically meant any code (not necessarily a callback) that might be invoked if certain conditions are satisfied just to set the result state to true. Simply speaking, I just want to get rid of the initial boolean[] passed and the final assertTrue(passed[0]) assuming that they are some kind of prologue and epilogue respectively and assuming that the initial state is set to false so the pass() should be invoked to set the state to true. No matter how the passed[0] is set to true, no matter where from. But unfortunately I have asked this question using the context of callbacks, however this is just an option, not a requirement. Thus the title of the question does not reflect what I really wanted to ask, but before the update some answers have been posted.

  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 9 years
    Thanks for the answer. I'm also thinking of idea of getting rid of the trailing asserts (this is a part of probably vague question too), but I'm not sure if it's a good idea.
  • Jon Skeet
    Jon Skeet over 9 years
    @LyubomyrShaydariv: Well what are you trying to test? If you don't care whether the callback was called or not, or what the argument was, then you can stub it out really easily...
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 9 years
    Thanks for the answer! Yes, Mockito is a really nice lib. To be honest, I didn't take the mocking libraries into account: I just wanted to get rid of the surrogate flag and the trailing asserts. With Mockito, verify must be properly invoked too. I just thought that the last assert might be implicit and "supplied" by the test runner.
  • fge
    fge over 9 years
    Well, as I said, it is only a value matcher; if you want, you can replace the argument by true, or even an ArgumentCaptor<Boolean>.
  • qwelyt
    qwelyt over 6 years
    Just the heading made me realise how easy it is to test callbacks with this. I have a class that accepts a Consumer<MyObject> parameter. Simply passing in list::add as the consumer made the testing a whole lot easier as now I can just verify that the size of the list is what I expected, rather than making some strange things with mocks. So +1.