Having a Spring Boot app, working with Spring Data JPA and H2 configured, it for sample/academic purposes.
a Repository is defined as:
public interface PersonaRepository extends CrudRepository<Persona, String> {
}
and then the following test
@ActiveProfiles(profiles={"h2"})
@DataJpaTest
class PersonaRepositorySliceTests {
@Autowired
private PersonaRepository personaRepository;
@Autowired
private TestEntityManager testEntityManager;
@Test
void countTest() {
long count = personaRepository.count();
assertThat(count).isEqualTo(33);
count = testEntityManager.getEntityManager()
.createQuery("SELECT COUNT(*) FROM Persona p")
.getResultList().size();
System.out.println("count: " + count);//prints 1
//assertThat(count).isEqualTo(33);//fails
count = testEntityManager.getEntityManager()
.createNativeQuery("SELECT COUNT(*) FROM persona")
.getResultList().size();
System.out.println("count: " + count);//prints 1
assertThat(count).isEqualTo(33);//fails
}
}
The
long count = personaRepository.count();
assertThat(count).isEqualTo(33);
pass, therefore the @Entity
classes were detected and of course the schema-h2.sql
and data-h2.sql
scripts were executed as expected.
Now the confusion is why
.createQuery("SELECT COUNT(*) FROM Persona p")
.createNativeQuery("SELECT COUNT(*) FROM persona")
Works but always returns 1, why not 33 how is expected?
"Seems" that TestEntityManager
in some way is not working with the data loaded by the data-h2.sql
file.
Secondary question:
Even if is yes, why is returned 1 and not 0? or is mandatory an extra configuration?
I read some tutorials about @DataJpaTest
and TestEntityManager
, among them, main source:
The former is a slice test and the latter is an alternative of EntityManager
for test.
In other tutorials I saw many examples of TestEntityManager
working with persist
and later retrieving the data (for 1 or more entities) within the same @Test
, so the insertion was accomplished manually.
Main Question:
TestEntityManager
type (approach) over the Custom CrudRepository<T,ID>
approach for testing purposes?The underlying issue has nothing to do with Spring Boot/Hibernate or your test setup.
The SQL statement SELECT COUNT(*) FROM Person
returns a single row with a single column that contains the number of counted rows:
$ SELECT COUNT(*) FROM Person;
COUNT(*)
----
4
That's why you have to call .getSingleResult()
from the result for your native or custom query:
assertThat(personRepository.count())
.isEqualTo(4);
assertThat(testEntityManager.getEntityManager()
.createQuery("SELECT COUNT(*) FROM Person p", Long.class)
.getSingleResult())
.isEqualTo(4L);
assertThat(testEntityManager.getEntityManager()
.createNativeQuery("SELECT COUNT(*) FROM Person")
.getSingleResult())
.isEqualTo(BigInteger.valueOf(4));
Spring Data JPA does the exact same thing under the hood for .count()
:
public long count() {
return (Long)this.em.createQuery(this.getCountQueryString(), Long.class).getSingleResult();
}
You can also directly inject the EntityManager
for your @DataJpaTest
test and don't need the TestEntityManager
for this:
@DataJpaTest
class ApplicationTests {
@Autowired
private TestEntityManager testEntityManager;
@Autowired
private EntityManager entityManager;
@Autowired
private PersonRepository personRepository;
@Test
void contextLoads() {
assertThat(personRepository.count())
.isEqualTo(4);
//With TestEntityManager
assertThat(testEntityManager.getEntityManager()
.createQuery("SELECT COUNT(*) FROM Person p", Long.class)
.getSingleResult())
.isEqualTo(4L);
assertThat(testEntityManager.getEntityManager()
.createNativeQuery("SELECT COUNT(*) FROM Person")
.getSingleResult())
.isEqualTo(BigInteger.valueOf(4));
//With EntityManager
assertThat(entityManager.createQuery("SELECT COUNT(*) FROM Person p", Long.class)
.getSingleResult())
.isEqualTo(4L);
assertThat(entityManager.createNativeQuery("SELECT COUNT(*) FROM Person")
.getSingleResult())
.isEqualTo(BigInteger.valueOf(4));
}
}
The TestEntityManager
provides helper methods to e.g. do a persist/flush/find operation (flush the in-memory persistence context and read the same entity from the database) with a single-liner. The TestEntityManager
is never mandatory.