Testing Java modules: the easiest way to check if a callback is called

I often work with methods that accept callbacks, and callbacks seem somewhat difficult to test. Consider the following scenario, if there is a method that accepts a callback using one method (for simplicity, I assume that the test method is synchronous), the following template template can only be written to provide a callback method call:

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

He looks like a surrogate. I would like to know: is there a more elegant way to test such code so that the code above looks 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 bit cleaner. Is this possible in JUnit, TestNG, or any other testing framework? Thank you


UPDATE

Sorry, I seem to have asked a vague question that really doesn't match what I wanted to ask. I basically meant any code (not necessarily a callback) that can be called if certain conditions are satisfied only to set the state of the result to true. Simply put, I just want to get rid of the initial boolean[] passed and the final assertTrue(passed[0]) , assuming that they are a kind of prologue and epilogue, respectively, and assume that the initial state is set to false , so pass() should be called to set the state to true . No matter how passed[0] set to true , no matter where it comes from. But unfortunately, I asked this question using the callback context, 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 the answers were sent before the update.

+5
source share
3 answers

This is usually what mockery can do for you.

With Mockito , for example:

 // 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 check mode ( only() ) and matching values ​​( any() ). You can do more ...

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

+9
source

Given that this is what you might need in several places, I would just create a named class for testing:

 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 } 

Then you can use something like:

 // Arrange... FakeCallback callback = new FakeCallback(); // Act... method(callback); // Assert assertTrue(callback.wasHandleCalled()); assertTrue(callback.wasSuccessful()); 

Instead, you can use a fake framework, but I personally find it often easier to create a single fake implementation than setting up mocks again. Both methods will work.

+7
source

List :: add as callback

When the task is to check the callback, which is a functional interface that takes one parameter (here the logical value can also be a string or any random type), it seems most concise to prepare the list, pass the List.add(e) method as a callback, and then check the contents 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); 

An alternate 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); 

Similarly, the counter class can serve to test a callback that does not accept any parameters. Here, using AtomicInteger, since this seems to be the only counterclass available in standard libs - the atomicity property is not required here:

 AtomicInteger callbackCounter = new AtomicInteger(); methodUnderTest(callbackCounter::incrementAndGet); // assert that the callback was called 5 times: assertEquals(5, callbackCounter.get()); 
+1
source

Source: https://habr.com/ru/post/1208541/


All Articles