I want to make an entity testing with with PostgreSQL testcontainers in my SPring boot 3.x app. First I try to insert some constraint violation records and then some valid records and then I want to delete all records, to continue testing.
In delete all records I get an error like this:
“row was updated or deleted by another transaction”.
AFAIK JPA tests run under transaction. Unfortunately the database is not in my scope, that’s why I cannot use @Version in the entity.
Thats my Entity:
@Entity
@Table(name= "E", schema = Const.SCHEME_NAME)
@Data
@NoArgsConstructor
@SuperBuilder
@EqualsAndHashCode
public class E {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
@EqualsAndHashCode.Include
private Long id;
@NotNull(message = "{name.not-blank}")
@Column(name="name", length = 150, nullable = false)
private String name;
my repository is a simple JpaRepository:
@Repository
public interface ERepository extends JpaRepository<E, Long> {
}
my test is:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ContextConfiguration(initializers = ETestApplication.TestContainersInitializer.class)
public class ETestApplication {
static class TestContainersInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public static List<E> VALID=List.of(E.builder()
.name("asd")
.build()
);
public static List<E> INVALID=List.of(E.builder()
.build()
);
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of(
"spring.datasource.url="+ postgresService.getJdbcUrl(),
"spring.datasource.username="+ postgresService.getUsername(),
"spring.datasource.password="+ postgresService.getPassword())
.applyTo(applicationContext.getEnvironment());
}
@Container
private static PostgreSQLContainer<?> postgresService = new
PostgreSQLContainer<>("postgres:16-alpine")
.withInitScript("sql/init.sql");
@LocalServerPort
protected int port;
@Autowired
protected ERepository repository;
@BeforeAll
public static void beforeAll() {
try {
postgresService.start();
}
catch (Exception e) {
e.printStackTrace();
}
}
@AfterAll
public static void afterAll() {
postgresService.stop();
}
protected void entityInsert(E e) {
E saved = repository.save(e);
Assertions.assertNotNull(saved);
E restored = repository.findById(saved.getId())
.orElseThrow(RuntimeException::new);
Assertions.assertNotNull(restored);
Assertions.assertEquals(saved, restored);
}
protected void entityUpdate(E e) {
E updated = repository.save(e);
Assertions.assertNotNull(updated);
Assertions.assertEquals(e.entity, updated);
}
protected void entityDelete(E e) {
repository.delete(e);
repository.findById(e.getId())
.ifPresent(d -> {
throw new RuntimeException("%s is not deleted".formatted(d));
});
}
@Test
@Order(1)
void agentServiceTest() {
INVALID.forEach(e -> {
Assertions.assertThrows(e.expectedException, () ->
repository.save(e.entity),
"constraints violated but no exception");
});
VALID.forEach(e -> {
entityInsert(e);
e.setName("bsd");
entityUpdate(e);
entityDelete(e.entity);
});
repository.deleteAll(); // Exception: Row was updated or deleted by another transaction
// continue testing...
}
}