transactionTemplate.execute((s) -> propertyDao.persist(copyOf(toSave).withNewResourceId(accountId)));If you mock TransactionTemplate then propertyDao.persist() will never be invoked. In my unit test PropertyDao is a mock so now I cannot use Mockito.verify() to check whether persist method has been invoked (it returns void).
private final PropertyDao propertyDao = mock(PropertyDao.class);Let's see how execute() method has been implemented:
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
The most important line:
result = action.doInTransaction(status);It simply means that our function:
transactionTemplate.execute((s) -> propertyDao.persist(copyOf(toSave).withNewResourceId(accountId)));is being invoked in the method so when you mocked the template it won't happen at all. How to deal with that ? My first thought was to use ArgumentCaptor to catch the parameter passed to execute method and invoke it but I think I found a better way.
class FunctionCallingTransactionTemplate extends TransactionTemplate {
@Override public <T> T execute(TransactionCallback<T> action) throws TransactionException {
final TransactionStatus irrelevantStatus = null;
return action.doInTransaction(irrelevantStatus);
}
}
In the code above I extend TransactionTemplate so that in only invokes the action passed to execute() method without other stuff.
I guess I'm gonna need this in many tests so we can create a simple trait:
public interface FunctionCallingTransactionTemplateTrait {
default TransactionTemplate functionCallingTransactionTemplate() {
return new FunctionCallingTransactionTemplate();
}
class FunctionCallingTransactionTemplate extends TransactionTemplate {
@Override public <T> T execute(TransactionCallback<T> action) throws TransactionException {
final TransactionStatus irrelevantStatus = null;
return action.doInTransaction(irrelevantStatus);
}
}
}
Now in my test I have:
public class SaveAccountAttributesTransactionTest implements FunctionCallingTransactionTemplateTrait {
private final ArgumentCaptor propertyCaptor = ArgumentCaptor.forClass(Property.class);
private final PropertyDao propertyDao = mock(PropertyDao.class);
private final TransactionTemplate transactionTemplate = functionCallingTransactionTemplate();
private final SaveAccountAttributesTransaction transaction = new SaveAccountAttributesTransaction(propertyDao, transactionTemplate);
...
}
And some test:
@Test
public void shouldUpdateOneValueAndPersistOther() throws Exception {
// given
when(propertyDao.fetchResourceProperties("root", ACCOUNT)).thenReturn(Lists.newArrayList(
propertyOf("firstProp", "2.21", "root"),
propertyOf("secondProp", null, null),
propertyOf("thirdProp", null, null),
propertyOf("fourthProp", null, null)
));
SaveAccountAttributesEvent event = new SaveAccountAttributesEvent("root", Lists.newArrayList(
propertyOf("firstProp", "2.22", "root"),
propertyOf("secondProp", null, null),
propertyOf("thirdProp", "1.11", "root"),
propertyOf("fourthProp","default", null)
));
// when
transaction.execute(event);
// then
verify(propertyDao).updatePropertyValue(anyString(), eq("2.22"));
verify(propertyDao).persist(propertyCaptor.capture());
assertThat(propertyCaptor.getAllValues()).extracting(Property::getResourceId, Property::getValue)
.containsOnly(tuple("root", "1.11"));
}
And it passess :) As you can see I verify behaviour of propertyDao which is being invoked by our extended TransactionTemplate.
Hope it helps.