xuanzhui
xuanzhui

Reputation: 1418

How to unit test java multiple thread

The issue is that I have a method starting a new thread for a time-consuming work. I want to test the callback result, but the child thread may still running, so as a result, what I get is not the right stub.

I think the code may explain itself:

public class JustAClass {
    //it is a callback for async   
    public interface JustACallBack {
        void callFunc(JustAResult result);
    }

    //this is the result interface
    public interface JustAResult {
    }

    //this is a real class for the interface
    public class JustAResultReal implements JustAResult{
        public JustAResultReal(String content) {this.content = content;}
        public String content;
    }

    //here is the key function
    public void threadFunc(final JustACallBack callBack) {
        BCCache.executorService.execute(new Runnable() {
            @Override
            public void run() {
                //just to simulate a time-consuming task
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //now we callback
                callBack.callFunc(new JustAResultReal("can you reach me"));
            }
        });
    }
}

and the test function could be(I am using mockito):

@Test
public void testThreadFunc() throws Exception {

    JustAClass justAClass = new JustAClass();

    JustAClass.JustACallBack callBack = Mockito.mock(JustAClass.JustACallBack.class);

    justAClass.threadFunc(callBack);

    //add this line, we can get the expected result
    Thread.sleep(1200);

    Mockito.verify(callBack).callFunc(captor.capture());

    System.out.println(((JustAClass.JustAResultReal)captor.getValue()).content);
}

I know we can add a sleep to wait and expect that the child thread would exit within the period, but could there be a better way? Actually how could I know how long the child thread would take? Setting a very long time can be an approach but just seems not very nice.

Upvotes: 2

Views: 11301

Answers (2)

Jonathan
Jonathan

Reputation: 5117

The general approach in @stalet's answer is close, but doesn't quite work since any assertion failures from a separate thread are not noticed by the main thread. Therefore your test will always pass, even when it shouldn't. Instead, try using ConcurrentUnit (which I authored):

@Test
public void testInvoke() throws Throwable {
    Waiter waiter = new Waiter();
    JustAClass justAClass = new JustAClass();
    JustAClass.JustACallBack callBack = new JustAClass.JustACallBack() {
        @Override
        public void callFunc(final JustAClass.JustAResult result) {
            waiter.assertNotNull(result);
            waiter.assertTrue(result instanceof JustAClass.JustAResultReal);
            waiter.resume();
        }
    };

    justAClass.threadFunc(callBack);
    waiter.await(1200, TimeUnit.SECONDS);
}

The key here is ConcurrentUnit's Waiter will properly report any assertions failures to the main test thread and the test will pass or fail as it should.

Upvotes: 2

stalet
stalet

Reputation: 1365

I aggree with @Gimbys comment about this is no longer a unit-test when you start testing the the threading aspect.

Nevertheless it is interesting as a way to integration-test a asynchronous invokation.

To avvoid sleep i tend to use the class CountDownLatch to wait for invokations. In order to count down you need an actuall implementation of the callback interface - so in my example I have made a mock implementation of this.

Since there is no actual methods to fetch the data - i am just testing that it is in fact a instance of the JustAReal interface.

@Test
public void testInvoke() throws Exception {

    final CountDownLatch countDownLatch = new CountDownLatch(1); //1 is how many invokes we are waiting for

    JustAClass justAClass = new JustAClass();
    JustAClass.JustACallBack callBack = new JustAClass.JustACallBack() {
        @Override
        public void callFunc(final JustAClass.JustAResult result) {
            assertNotNull("Result should not be null", result);
            assertTrue("Result should be instance of JustAResultReal", result instanceof JustAClass.JustAResultReal);
            countDownLatch.countDown();
        }
    };

    justAClass.threadFunc(callBack);
    if(!countDownLatch.await(1200, TimeUnit.MILLISECONDS)){
        fail("Timed out, see log for errors");
    }

}

Upvotes: 1

Related Questions