4

I am new to Junit and come across this problem recently. I am not able code write test cases wherever I used CompletableFuture in my code. Like below Java file

Updated

AuditService.java

@Autowired
Executor existingThreadPool;

@Override
public void auditData(List<ErrorDetails> alertList) {
    CompletableFuture.runAsync(() -> {
        if (alertList.isEmpty())
            //privateMethodCall1
        else
           //privateMethodCall2
    }, existingThreadPool);
}

I followed this link and tried below solution still getting NPE for CompletableFuture Like below error.

AuditServiceTest.java

@InjectMock
AuditService auditService;

@Mock
private CompletableFuture<Void> completableFuture = null;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    completableFuture = CompletableFuture.runAsync(new Runnable() {
        @Override
        public void run() {}
    },Executors.newSingleThreadExecutor());
}

@Test
public void shouldAuditData() {
    List<ErrorDetails> alertList = new ArrayList();
    auditService.auditData(alertList);
}

ERROR

java.lang.NullPointerException
    at java.util.concurrent.CompletableFuture.screenExecutor(CompletableFuture.java:415)
    at java.util.concurrent.CompletableFuture.runAsync(CompletableFuture.java:1858)
    at com.service.impl.AuditService.auditData(AuditService.java:15)
    at com.service.impl.AuditServiceTest.shouldAuditData(AuditServiceTest.java:249)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Mayur
  • 864
  • 6
  • 14
  • 25
  • Why do you need to mock CompletableFuture? As I think, you are testing that externalServiceCall1 must be called if alertList is empty, and if it's not empty - externalServiceCall2. So you need to mock externalService, and create two tests. first is `auditService.auditData(emptyList)`, second `auditService.auditData(notEmptyList)` – Alexander Terekhov May 16 '19 at 08:01
  • Hi Alex, this is just an example , most of the cases I have actual code, present instead of externalservice call that I need to cover. Is there any way I can do it? – Mayur May 16 '19 at 08:12
  • You can use Powermock to mock static methods: In your case, mock CompletableFuture.runAsync(). Here is the example: https://stackoverflow.com/questions/10583202/powermockito-mock-single-static-method-and-return-object But you need it only when it's impossible to mock code inside of Runnable. – Alexander Terekhov May 16 '19 at 09:22
  • Hi @Alexander, I dont want to actually mock the Static method, I want unit test case should run block of code writtern inside the CompletableFuture.runAsync block like (if else code snippet). – Mayur May 16 '19 at 10:48

2 Answers2

3

You need to test your logic and you don't need to mock the static method CompletableFuture.runAsync(...). So your test should look like normal test with exception that you need to wait some time to be sure that asynchronous code is executed, because it is not executed in the same thread. So for the moment I will give you example that you can use with Thread.sleep() which is not good convention, in additional question you can ask how to avoid usages of Thread.sleep().

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@RunWith(MockitoJUnitRunner.class)
public class AuditServiceTest {

    @Mock
    Service serviceMock;

    @Test
    public void shouldAuditData() {
        AuditService auditService = new AuditService(serviceMock);
        List<Object> alertList = new ArrayList();
        auditService.auditData(alertList);
        // you can wait with Thread.sleep() 
        // because the execution is asynchronous
        Mockito.verify(serviceMock).method1();
        Mockito.verify(serviceMock, Mockito.never()).method2();
    }
}

class AuditService {

    Executor existingThreadPool = Executors.newSingleThreadExecutor();
    Service service;

    public AuditService(Service service) {
        this.service = service;
    }

    public void auditData(List<Object> alertList) {
        CompletableFuture.runAsync(() -> {
            if (alertList.isEmpty()) {
                service.method1();
            } else {
                service.method2();
            }
        }, existingThreadPool);
    }
}
class Service {
    public void method1(){};
    public void method2(){};
}
Borislav Markov
  • 1,495
  • 11
  • 12
  • 1
    +1. Also if `auditData` returned the `CompletableFuture` generated by `runAsync` then there would be no need to Thread.sleep, you could just do `auditService.auditData(alertList).join();`, which would only return once the operation was complete. – MikeFHay May 16 '19 at 10:07
  • Thanks @Borislav Markov, but what will be the case if in runAsync block, I have private method call instead of external Service call ???, How to write it in this case. I have updated the class code. – Mayur May 16 '19 at 10:30
  • Hi @Mayur, It depends case to case. If it is private call, try to verify it with mockito or make it package local and not private and verify it with mockito. If this is not possible, then go along the chain of calls and see what you can verify. All those private calls - what they do? If they produce result, then take the result at some point and verify it. – Borislav Markov May 16 '19 at 10:56
  • it works if i go with CommonForkJoin instead of CustomizedThreadPool. but that should be not the case – Mayur May 19 '19 at 07:27
2

in AuditService class , Executor is autowired. that is perfect setup for unit tests. what you have to do is , come up with separate configuration for test and Executor implementation should be a inline executor (you can provide your own implementation which calls runnable.run in the same calling thread). To do this you can use some implementations provided spring-test. ex: AbstractJUnit4SpringContextTests

if you dont like to go with spring-test support, now you have injected mock Executor to AuditService. so you can mock the execute method with providing custom stub.Answer and execute the runnable.run.

Mockito.doAnswer(new Answer() {
      public Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        ((Runnable)args[0]).run();
        return null; // void method, so return null
      }
    }).when(executor).execute(Mockito.any(Runnable.class));
hunter
  • 3,963
  • 1
  • 16
  • 19