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 ArgumentCaptorAnd some test: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); ... }
@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.