I have a simple Repository:
public interface ReviewRepository extends CrudRepository<ReviewEntity, Integer> {
@Transactional(readOnly = true)
List<ReviewEntity> findByProductId(int productId);
}
I want to test it using test containers I followed the procedures and wrote my test case:
public abstract class MySqlTestBase {
private static MySQLContainer database = new MySQLContainer("mysql:5.7.32");
static {
database.start();
}
@DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", database::getJdbcUrl);
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
}
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class PersistTests extends MySqlTestBase {
@Autowired
private ReviewRepository repository;
private ReviewEntity savedEntity;
@BeforeEach
void setupDb() {
repository.deleteAll();
ReviewEntity entity = new ReviewEntity(1, 2, "author1");
savedEntity = repository.save(entity);
assertEqualsReview(entity, savedEntity);
}
@Test
void update() {
savedEntity.setAuthor("author2");
repository.save(savedEntity);
ReviewEntity foundEntity = repository.findById(savedEntity.getId()).get();
assertEquals(1, (long)foundEntity.getVersion());
assertEquals("author2", foundEntity.getAuthor());
}
}
my ReviewEntity also is written like:
@Entity
public class ReviewEntity {
@Id @GeneratedValue
private int id;
@Version
private int version;
private int productId;
private int reviewId;
private String author;
public ReviewEntity(int productId, int reviewId, String author) {
this.productId = productId;
this.reviewId = reviewId;
this.author = author;
}
// setter and getter
}
When I run this test it fails at the assertEquals(1, (long)foundEntity.getVersion());
line with this message:
expected: <1> but was: <0>
Expected :1
Actual :0
But I update the ReviewEntity
class and according to the documentation the @Version
field should automatically increases but this not happens. what part of my test is wrong?
CodePudding user response:
If you look at the default implementation of save
method in CrudRepository
interface in the SimpleJpaRepository
class you will see save
method is implemented like:
@Transactional
@Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
meaning it is marked with @Transactional
with Required
as its propagation level(it is default)
Required propagation works like this:
REQUIRED is the default propagation. Spring checks if there is an active transaction, and if nothing exists, it creates a new one. Otherwise, the business logic appends to the currently active transaction
and for DataJpaTest
annotation comment section says:
By default, tests annotated with @DataJpaTest are transactional and roll back at the end of each test
So for method update
in your test a transaction is going to be created and the save method in repository.save(savedEntity);
is going to be appended to that transaction. meaning it is committed only if that transaction successfully committed and we now know that's not going to happen.
A workaround for this problem probably would be to annotate test class with @Transactional(propagation = NOT_SUPPORTED)
to suspends the currently running transaction then for repository.save(savedEntity);
a transaction is going to be created and committed at the end of save
method and then you can proceed in your test.