Issue
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?
Solution
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.
Answered By - h.a.
Answer Checked By - Clifford M. (JavaFixing Volunteer)