Search code examples
javaspring-bootspring-boot-test

When is mandatory use TestEntityManager in testing?


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:

  • Is it the expected behaviour?

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:

  • So when is necessary work with the TestEntityManager type (approach) over the Custom CrudRepository<T,ID> approach for testing purposes?

Solution

  • 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.