As mentioned above, in order for my service under test to be correctly wired up to be Proxied, it needs to be annotated in the test as @Autowired
.
And, in order to make my service @Autowired
while still being able to mock a failure, I needed to change my dependencies from being @Autowired
to using @SpyBean
.
In addition, @MybatisTest
is itself @Transactional
, so that masks whether the rollback in my application code is actually working. So, I have fallen back to using @SpringBootTest
.
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class ThingServiceIntegrationTest extends AbstractPostgresJupiterTest {
// NOTE: AbstractPostgresJupiterTest sets up a PostgreSQLContainer and initializes datasource properties
@SpyBean
private ThingMapper mapper;
@SpyBean
private ThingDao dao;
@Autowired
private ThingService service;
@Test
@Description("Should rollback on error")
public void rollbackOnError() {
final List<ThingRequest> createRequests = Stream.of(1, 3)
.map(i -> generateThingRequest())
.toList();
final ThingRequest badRequest = createRequests.getLast().withName("Bad Request");
final List<ThingRequest> all =
Stream.concat(createRequests.stream(), Stream.of(badRequest)).toList();
doThrow(new MyBadRequestException("bad")).when(dao)
.createThing(argThat(thing -> thing.id().equals(badCreate.id())));
assertThrows(MyBadRequestException.class, () -> thingService.createUpdateThings(all));
// should rollback all inserts
createRequests.forEach(req -> {
assertTrue(dao.getByName(req.name()).isEmpty());
});
}
}
Finally, one other tweak I had to do was due to MyService
having an @Autowired
constructor (vs autowired fields). This seemed to cause problems for the @SpyBeans
. The fix for this was to mark the constructor parameters as @Lazy
.
With all these changes, I was able to use @Transactional
instead of the transaction manager approach.
@Service
public class ThingService {
private final ThingDao thingDao;
@Autowired
public ThingService(@Lazy final ThingDao dao) {
this.thingDao = dao;
this.txManager = txManager;
}
@Transactional(rollbackFor = Throwable.class)
public void createOrUpdate(final List<ThingRequest> requests) {
requests.forEach(c -> thingDao.createThing(ThingModel.fromCreateRequest(c)));
}
public void createUpdateThings(final List<ThingRequest> requests) throws ControllerBadRequestException {
try {
createOrUpdate(requests);
} catch (final Throwable t) {
logger.error("A database exception occurred during createUpdateThings", t);
throw new MyBadRequestException(t);
}
}
}
Hope this is helpful for someone else.