In my Spring integration tests (using JUnit 5. My test class is annotated @SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
, I'm trying to call repository.deleteAll()
in my @AfterEach
method.
Looking at the SQL in the logs, it seems that nothing is executed; and indeed, in the next tests, the entity with the same ID cannot be created because it already exists – meaning something prevented the database. I have played with different transaction types (propagation, isolation...) as other questions mention, but to no avail.
Interestingly, though, calling repository.deleteAllInBatch()
instead of deleteAll()
does work: all the tests pass.
What is going on?
EDIT: Adding code.
@Transactional
@SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
public class DeviceResourceIT {
@Autowired DeviceRepository deviceRepository;
@Autowired DeviceService lotService;
@Autowired private MappingJackson2HttpMessageConverter jacksonMessageConverter;
@Autowired private ExceptionTranslator exceptionTranslator;
private MockMvc mockMvc;
private Logger log = LoggerFactory.getLogger(DeviceResourceIT.class);
@PostConstruct
void setup() {
DeviceResource deviceResource = new DeviceResource(deviceService);
mockMvc = MockMvcBuilders.standaloneSetup(deviceResource)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.build();
}
@Test
public void getLot() throws Exception
{
String lotID;
String wrongLotID = "aRandomString";
final List<DeviceRequestDTO> requests = Arrays.asList(
new DeviceRequestDTO("l1", "ble1"),
new DeviceRequestDTO("l2", "ble2"),
new DeviceRequestDTO("l3", "ble3")
);
LotDTO lotDTO = new LotDTO(requests);
MvcResult mvcResult = mockMvc.perform(post("/api/admin/lot")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(toJsonString(lotDTO)))
.andDo(print())
.andExpect(status().isOk())
.andReturn();
LotDTO returnedLotDTO = convertJsonBytes(mvcResult.getResponse().getContentAsByteArray(), LotDTO.class);
lotID = returnedLotDTO.getId();
log.info("{the lot id is : }" + lotID);
// retrieve the Lot with the right lot ID
mockMvc.perform(get("/api/admin/lot/{lot_id}", lotID))
.andDo(print())
.andExpect(status().isOk());
}
@AfterEach
public void tearDown() {
try {
log.info("{}", deviceRepository.count());
// Returns: 3
deviceRepository.deleteAll();
log.info("{}", deviceRepository.count());
// Returns: 3
// ... but we would expect to see 0, given the call to
// deleteAll() just above...
} catch (Exception e) {
Fail.fail(e.getMessage());
}
}
}
This was a strange error to diagnose. It turned out that I had implemented isNew()
(of Persistable<T>
) wrong, and that it was returning true.
As a result the call to SimpleJpaRepository#delete(T entity)
performs this check:
public void deleteAll(T entity)
// [...]
if (entityInformation.isNew(entity)) {
return;
}
// actually delete...
// [...]
}
My entities were returning isNew() = true
so, naturally, the repository was just skipping all the entities, and never deleting them. Meanwhile, deleteAllInBatch()
does not perform that check.
To fix this, I have stopped implementing Persistable
and made my entities Serializable
instead.
Example of minimal implementation that would work without switching to Serializable:
class XXX implements Persistable<T>
{
@Id private String id;
// (getters, setters, logic, etc...)
// implement the `T getId()` method for the Persistable<T> interface,
// most likely this will simply be a getter for the id field above
// transient fields are not stored but provide information to Hibernate/JPA
// regarding the status of this entity instance
@Transient private Boolean persisted = false;
@PostPersist @PostLoad void setPersisted() {
persisted = true;
}
@Override public boolean isNew() {
return !persisted;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With