Writing junit(mockito) for a public method created inside a private method

Viewed 120

I want my test to cover the code written inside afterCompletion(). I've tried mocking the public method which calls addTransactionLifeCycleMethods(), but it doesn't go inside TransactionSynchronizationManager. Here's the code snippet

private void addTransactionLifeCycleMethods(Map<Integer, ForecastRunDTO> newRunDTOs, Boolean isSystemTriggeredActualComputation) {
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCompletion(int status) {
            // Cover this using mockito
            }
        }
    });
}

Note: TransactionSynchronizationManager and TransactionSynchronizationAdapter are both abstract classes in org.springframework.transaction.support;

2 Answers

Elaborating on my suggestion from the comments:

First, I'd refactor the "production" code so that the TransactionSynchronizationAdapter is produced by a method:

public class MyRealClass {
    private void addTransactionLifeCycleMethods(
        Map<Integer, ForecastRunDTO> newRunDTOs, Boolean isSystemTriggeredActualComputation) {
        TransactionSynchronizationManager.registerSynchronization(
            getTransactionSynchronizationAdapter());
    }

    protected TransactionSynchronizationAdapter getTransactionSynchronizationAdapter() {
        return new TransactionSynchronizationAdapter() {
                @Override
                public void afterCompletion(int status) {
                    // Cover this using mockito
                    }
                }
            });
        };
    }

    // rest of the class....
}

You can then use mockito so spy that method and return a mock object with any arbitrary behavior you need for your test:

public class MyRealClassTest {
    private MyRealClass underTest;

    @BeforeEach
    public void setUp() {
        TransactionSynchronizationAdapter mockTSA = 
            Mockito.mock(TransactionSynchronizationAdapter.class);
        // Add behavior to the mock using Mockito.when

        underTest = Mockito.spy(new MyRealClass());
        Mockito.doReturn(mockTSA).when(underTest).getTransactionSynchronizationAdapter();
    }

    // tests ...
}

This worked for me. First, calling the public method() so that the line TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() could be executed. Then we're fetching the object created and using it to access afterCompletion()

            public MainClass {
                  public void method() {
                  .......
                  addTransactionLifeCycleMethods(Map<Integer, ForecastRunDTO> newRunDTOs, Boolean isSystemTriggeredActualComputation);
                  }
                  private void addTransactionLifeCycleMethods(Map<Integer, ForecastRunDTO> newRunDTOs, Boolean isSystemTriggeredActualComputation) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                    @Override
                    public void afterCompletion(int status) {
                        // Cover this using mockito
                        }
                });
            }
       }
            
 
            Answer:
            TestClass {
            
            @InjectMocks
            MainClass manager;
            
            @BeforeEach
                public void init() {
                    TransactionSynchronizationManager.initSynchronization();
                }
            
            @AfterEach
                public void clear() {
                    TransactionSynchronizationManager.clear();
                }

            @Test
            void Test {
                   manager.method();
                   TransactionSynchronizationManager.getSynchronizations().get(0).afterCompletion(anyInt());        
            }

}
Related