Search code examples
javaunit-testingmockitospring-transactions

Integration test a manual transaction with transaction template


I am getting null pointer exceptions on my transaction template when I try to test my method that uses manual transactions. When I am running the application in Spring Boot it works as expected.

@Autowired
TransactionTemplate template;

public CompletableFuture<MyResultEntity> addToA(BInput input) {
    return CompletableFuture
        .supplyAsync(
            () -> template.execute(status -> {
              A a = aRepository.findOne(input.getA());
              List<B> addedBs = saveBs(input.getB(), a);
              return new MyResultEntity(a, addedBs);
            }), MyCustomExecutor());
}

I have tried using a mock template and inject it like this:

@Mock
private TransactionTemplate transactionTemplate;

@InjectMocks
private MyClass myClass;

I have also tried annotating my test with:

@RunWith(SpringJUnit4ClassRunner.class)

When debugging this configuration the template is actually injected and is not null any more. But since I am interested in testing the actions in the transactions I do not wish to mock it so i use:

when(transactionTemplate.execute(Mockito.any())).thenCallRealMethod();

This throws a new null pointer exception though since the transaction template tries to use the TransactionManager and that is still null.

How can I unit test my method calls inside the the transaction template?


Solution

  • What I normally do is not to call the real method, but to just mock the real behavior instead. Calling the real method in the mock will fail, because a mock is not managed inside of springs injection context. Well, to be exact, you can make them exist inside the injection context by adding them to a test configuration (plain SpringMVC) or using @MockBean (spring boot). But still they are just injected as a dependency. But won't receive any dependencies. For unit tests this is most often the desired behavior.

    So just do something like:

    when(_transactionTemplate.execute(any())).thenAnswer(invocation -> invocation.<TransactionCallback<Boolean>>getArgument(0).doInTransaction(_transactionStatus));
    

    _transactionStatus can be a mock itself to test the usage of the state inside the callback.

    Mocking is what mocks are used for :)