javahibernatetransactionsmockito

Executing lambda functions inside transactionExecutor threads for unit tests


i have few methods that need to be iterated in new transactions but facing errors when i try to write unit tests for them

@Inject
  private SchedulerUtil schedulerUtil;
  @Inject
  private CompletableSelfReferralFilter filter;
  @Inject
  private SelfReferralAutoCloseHandler handler;
  @Inject
  private DemandForCareDAO demandForCareDAO;
  @Inject
  private AutoClosedSelfReferralInformationHelper autoClosedSelfReferralInformationHelper;
  @Inject
  private TransactionExecutor transactionExecutor;

  private int closedCount = 0;
  private int totalCount = 0;
  private Stopwatch stopwatch;
  private DateTime initialStartedTime;
  private DateTime lastProcessedReferralDateTime;

@Override
  public void schedule(SelfReferralAutoCloseConfigurationData configuration)
  {
    stopwatch = Stopwatch.createStarted();

    lastProcessedReferralDateTime = autoClosedSelfReferralInformationHelper.getCreatedDateTime(configuration);
    initialStartedTime = lastProcessedReferralDateTime;
    long pageTotalCount;
    long pageCount;

    do
    {
      final long[] pageTotalCountHolder = new long[1];
      final long[] pageCountHolder = new long[1];

      transactionExecutor.runInNewReadOnlyTransaction(() -> {
        final Page<DemandForCare> demandForCarePage = loadDemandForCarePage(configuration);
        List<DemandForCare> openRequests = demandForCarePage.getContent();

        pageTotalCountHolder[0] = demandForCarePage.getTotal();
        pageCountHolder[0] = demandForCarePage.getContent().size();

        complete(configuration, openRequests);
      });

      pageTotalCount = pageTotalCountHolder[0];
      pageCount = pageCountHolder[0];
    }
    while (
      schedulerUtil.isWithinExecutionTimeLimit(configuration.getExecutionTime(), stopwatch.elapsed(TimeUnit.HOURS)) &&
      pageTotalCount > pageCount);

    infoLogs(configuration, pageTotalCount == pageCount);

    totalCount = 0;
    closedCount = 0;
  }

  private void complete(SelfReferralAutoCloseConfigurationData configuration, List<DemandForCare> openRequests)
  {
    transactionExecutor.runInNewReadOnlyTransaction(() -> {
      System.out.println("Processing " + openRequests.size() + " self referrals to complete.");
      for (DemandForCare dfc : openRequests)
      {
        try
        {
          if (filter.filter(dfc, configuration))
          {
            handler.autoComplete(dfc.getId(), configuration.getCloseReason(), configuration.getMode());
            closedCount++;
            LOG.debug("Completed referral Id: {}", dfc.getId());
          }
        }
        catch (Exception e)
        {
          if (e.getMessage() != null)
          {
            LOG.debug("Couldn't complete referral Id: {} and throws the error as {}", dfc.getId(), e.getMessage());
          }
          else
          {
            LOG.debug("Couldn't complete referral Id: {} and throws the error as ", dfc.getId(), e);
          }
        }
        totalCount++;
        lastProcessedReferralDateTime = dfc.getCreationInfo().getCreatedDateTime();
      }
    });
  }

  private Page<DemandForCare> loadDemandForCarePage(SelfReferralAutoCloseConfigurationData configurationData)
  {
    return demandForCareDAO.findInProgressSelfReferralsCreatedAfter(lastProcessedReferralDateTime,
                                                                    new PagingParameters(0,
                                                                                         configurationData.getPageSize(),
                                                                                         Collections.emptyList()));
  }
public class TransactionExecutor {
  private static TransactionExecutor instance;

  public TransactionExecutor() {
  }

  public static TransactionExecutor getInstance() {
    return instance;
  }

  @Inject
  public void setInstance(TransactionExecutor transactionExecutor) {
    instance = transactionExecutor;
  }

  @Transactional(
    propagation = Propagation.REQUIRES_NEW
  )
  public void runInNewTransaction(ThrowingRunnable<Exception> runnable) {
    ExceptionWrapper.uncheck(runnable);
  }

  @Transactional(
    propagation = Propagation.REQUIRES_NEW
  )
  public <V> V runInNewTransaction(Callable<V> callable) {
    return ExceptionWrapper.uncheck(callable);
  }

  @Transactional(
    readOnly = true,
    propagation = Propagation.REQUIRES_NEW
  )
  public void runInNewReadOnlyTransaction(ThrowingRunnable<Exception> runnable) {
    ExceptionWrapper.uncheck(runnable);
  }

  @Transactional(
    readOnly = true,
    propagation = Propagation.REQUIRES_NEW
  )
  public <V> V runInNewReadOnlyTransaction(Callable<V> callable) {
    return ExceptionWrapper.uncheck(callable);
  }

  @Transactional(
    propagation = Propagation.REQUIRED
  )
  public void runInTransaction(ThrowingRunnable<Exception> runnable) {
    ExceptionWrapper.uncheck(runnable);
  }

  @Transactional(
    propagation = Propagation.REQUIRED
  )
  public <V> V runInTransaction(Callable<V> callable) {
    return ExceptionWrapper.uncheck(callable);
  }
}

These methods are the focus of unit tests and my current test looks like this

@Mock
  private SelfReferralAutoCloseHandler handler;
  @Mock
  private DemandForCareDAO demandForCareDAO;
  @Mock
  private CompletableSelfReferralFilter filter;
  @Mock
  private TermDAO termDAO;
  @Mock
  private AutoClosedSelfReferralInformationHelper autoClosedSelfReferralInformationHelper;
  @Mock
  private SchedulerUtil schedulerUtil;
  @Mock
  private TransactionExecutor transactionExecutor;
  @InjectMocks
  private SelfReferralAutoCloseScheduler scheduler;

@BeforeMethod
  public void setUp()
  {
    MockitoAnnotations.initMocks(this);

    when(autoClosedSelfReferralInformationHelper.getCreatedDateTime(any())).thenReturn(new DateTime());


//    reset(transactionExecutor);
//    when(transactionExecutor.runInNewReadOnlyTransaction(any(Callable.class)))
//      .thenAnswer(invocation -> {
//        Callable<?> callable = invocation.getArgument(0);
//        return callable.call();
//      });
  }

  @Test
  public void testAutoComplete_whenReachingExecutionTime_thenCallingToComplete() throws Exception {
    when(schedulerUtil.isWithinExecutionTimeLimit(anyInt(), anyLong())).thenReturn(true).thenReturn(false);
    final SelfReferralAutoCloseConfigurationData configurationData = mock(SelfReferralAutoCloseConfigurationData.class);
    when(configurationData.getExecutionTime()).thenReturn(1);
    final Term closeReason = mock(Term.class);
    when(configurationData.getCloseReason()).thenReturn(closeReason);
    when(closeReason.getValue()).thenReturn("closeReason");
    when(configurationData.getMode()).thenReturn(SelfReferralAutoClosureSchedulerMode.CLOSE_PAST_REFERRALS);
    when(configurationData.getPageSize()).thenReturn(3);

    List<DemandForCare> demandForCares = List.of(
      getDemandForCare(getDFCId("123")),
      getDemandForCare(getDFCId("234")),
      getDemandForCare(getDFCId("345"))
    );
    Page<DemandForCare> demandForCarePage = new PageImpl<>(demandForCares, 9, 0);

    List<DemandForCare> demandForCares2 = List.of(
      getDemandForCare(getDFCId("1231")),
      getDemandForCare(getDFCId("2341")),
      getDemandForCare(getDFCId("3451"))
    );
    Page<DemandForCare> demandForCarePage2 = new PageImpl<>(demandForCares, 6, 0);

    doReturn(Collections.singletonList(closeReason)).when(termDAO).findByValues(any(String[].class));
    doReturn(demandForCarePage, demandForCarePage2).when(demandForCareDAO).findInProgressSelfReferralsCreatedAfter(any(DateTime.class), any(PagingParameters.class));
    when(filter.filter(any(DemandForCare.class), any(SelfReferralAutoCloseConfigurationData.class))).thenReturn(true);
    doNothing().when(handler).autoComplete(any(DemandForCare.Id.class), any(Term.class), any(SelfReferralAutoClosureSchedulerMode.class));

    ArgumentCaptor<Callable<?>> callableCaptor = ArgumentCaptor.forClass(Callable.class);

    // Capture the lambda passed to transactionExecutor
    doAnswer(invocation -> {
      Callable<?> callable = callableCaptor.getValue();
      return callable.call();
    }).when(transactionExecutor).runInNewReadOnlyTransaction(callableCaptor.capture());

    scheduler.schedule(configurationData);

    verify(transactionExecutor, times(4)).runInNewReadOnlyTransaction(any(Callable.class));
    verify(handler, times(3)).autoComplete(any(DemandForCare.Id.class), eq(closeReason), eq(SelfReferralAutoClosureSchedulerMode.CLOSE_PAST_REFERRALS));
  }

I have tried different ways to make the mocks work for transactionExecutor but seems like its trying to match the argument passed into the lambda instead of executing them. since a good portion of methods are not executed because they are inside the thread and because of that verifies fail. Following is the error stack i received after executing the code.

Argument(s) are different! Wanted:
transactionExecutor.runInNewReadOnlyTransaction(
    <any java.util.concurrent.Callable>
);
-> at se.cambio.platform.sdk.server.util.TransactionExecutor.runInNewReadOnlyTransaction(TransactionExecutor.java:84)
Actual invocations have different arguments:
transactionExecutor.runInNewReadOnlyTransaction(
    se.cambio.referral.taskservice.selfreferral.SelfReferralAutoCloseScheduler$$Lambda$202/0x0000011f63366650@119290b9
);
-> at se.cambio.referral.taskservice.selfreferral.SelfReferralAutoCloseScheduler.schedule(SelfReferralAutoCloseScheduler.java:70)

    at se.cambio.platform.sdk.server.util.TransactionExecutor.runInNewReadOnlyTransaction(TransactionExecutor.java:84)
    at se.cambio.referral.taskservice.selfreferral.SelfReferralAutoCloseSchedulerTest.testSchedule_whenExecutionTimeLimitExceeded_thenProcessingStops_test(SelfReferralAutoCloseSchedulerTest.java:99)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:583)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:719)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:989)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at org.testng.TestRunner.privateRun(TestRunner.java:648)
    at org.testng.TestRunner.run(TestRunner.java:505)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
    at org.testng.SuiteRunner.run(SuiteRunner.java:364)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
    at org.testng.TestNG.runSuites(TestNG.java:1049)
    at org.testng.TestNG.run(TestNG.java:1017)
    at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:65)
    at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:105)

how can i fix this?

edit : also included dependency injections of fields


Solution

  • Your lambdas do not return anything. Consequently, they cannot be assigned to the SAM type Callable<V> (method V call()).

    It looks like you are calling a different overload: TransactionExecutor.runInNewReadOnlyTransaction(ThrowingRunnable)

    This code looks fishy as well – you want to access the arguments through invocation instead:

        doAnswer(invocation -> {
          Callable<?> callable = callableCaptor.getValue();
          return callable.call();
        }).when(transactionExecutor).runInNewReadOnlyTransaction(callableCaptor.capture());
    

    I don't see the captor used elsewhere for verification, so you might want to remove it altogether.

    The fix is probably trivial – migrate to the correct type and access the actual method argument:

    // Invoke the lambda passed to transactionExecutor
    doAnswer(invocation -> {
            invocation.<ThrowingRunnable>getArgument(0).run();
            return null;
        })
        .when(transactionExecutor)
        .runInNewReadOnlyTransaction(any(ThrowingRunnable.class));
    
    scheduler.schedule(configurationData);
    
    verify(transactionExecutor, times(4))
        .runInNewReadOnlyTransaction(any(ThrowingRunnable.class));