Main Tutorials

Testing Spring Data JPA with @DataJpaTest

This article shows how to use @DataJpaTest to test the Spring Data JPA application.

Technologies used:

  • Spring Boot 3.1.2
  • Spring Data JPA (Hibernate 6 is the default JPA implementation)
  • H2 in-memory database
  • Maven
  • Java 17
  • JUnit 5

Table of contents:

1. Project Directory

project directory

2. Spring Data JPA – Entity and Repository

The below example refers to the previous Spring Boot + Spring Data JPA example.

2.1 Project dependencies.

pom.xml

<dependencies>

      <!-- Spring Data JPA -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>

      <!-- in-memory database  -->
      <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
      </dependency>

      <!-- Testing -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>

  </dependencies>

2.2 An Book class, JPA entity.

Book.java

package com.mkyong;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import java.math.BigDecimal;
import java.time.LocalDate;

@Entity
public class Book {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String title;
  private BigDecimal price;
  private LocalDate publishDate;

  //getters , setters, constructor ...
}

2.3 Later, we will use the @DataJpaTest annotation to test the below BookRepository.

BookRepository.java

package com.mkyong;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDate;
import java.util.List;

public interface BookRepository extends JpaRepository<Book, Long> {

  List<Book> findByTitle(String title);

  // Custom Query
  @Query("SELECT b FROM Book b WHERE b.publishDate > :date")
  List<Book> findByPublishedDateAfter(@Param("date") LocalDate date);

}

3. @DataJpaTest

The @DataJpaTest annotation does the following stuff:

  • It scans the @Entity classes and Spring Data JPA repositories.
  • Set the spring.jpa.show-sql property to true and enable the SQL queries logging.
  • Default, JPA test data are transactional and roll back at the end of each test; it means we do not need to clean up saved or modified table data after each test.
  • Replace the application data source, run and configure the embedded database on classpath.

Below is a @DataJpaTest annotation example to test the JPA application.

BookRepositoryTest.java

package com.mkyong;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
public class BookRepositoryTest {

  // Alternative for EntityManager
  // Optional in this case, we can use bookRepository to do the same stuff
  @Autowired
  private TestEntityManager testEM;

  @Autowired
  private BookRepository bookRepository;

  @Test
  public void testSave() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));

      //testEM.persistAndFlush(b1); the same
      bookRepository.save(b1);

      Long savedBookID = b1.getId();

      Book book = bookRepository.findById(savedBookID).orElseThrow();
      // Book book = testEM.find(Book.class, savedBookID);

      assertEquals(savedBookID, book.getId());
      assertEquals("Book A", book.getTitle());
      assertEquals(BigDecimal.valueOf(9.99), book.getPrice());
      assertEquals(LocalDate.of(2023, 8, 31), book.getPublishDate());


  }

}

3.1 Disable the SQL query logging in @DataJpaTest

Default, the @DataJpaTest enabled the SQL query logging, which means the repository tests may generate the following SQL queries:

Terminal

Hibernate: insert into book (price,publish_date,title,id) values (?,?,?,?)
Hibernate: update book set price=?,publish_date=?,title=? where id=?
Hibernate: select b1_0.id,b1_0.price,b1_0.publish_date,b1_0.title from book b1_0 where b1_0.title=?

We can set the @DataJpaTest attribute value spring.jpa.show-sql property to false to turn off the SQL query logging.

BookRepositoryTest.java

// disabled or turn off the SQL query logging
@DataJpaTest(showSql = false)
public class BookRepositoryTest {
  //...
}

3.2 Disable the transactional and roll back in @DataJpaTest

Default, the @DataJpaTest tests data are transactional and roll back at the end of each test; we can add the @Transactional(propagation = Propagation.NOT_SUPPORTED) to turn it off.

BookRepositoryTest.java

import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

// disabled or turn off the transactional and roll back
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class BookRepositoryTest {
  //...
}

3.3 @AutoConfigureTestDatabase

Default, the @DataJpaTest uses @AutoConfigureTestDatabase to replace the application data source and run and configure the embedded database on classpath. We can turn this off by adding an attribute replace = AutoConfigureTestDatabase.Replace.NONE to the @AutoConfigureTestDatabase annotation.

BookRepositoryTest.java

@DataJpaTest
// We dont want the H2 in-memory database
// We will provide a custom `test container` as DataSource, don't replace it.
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class BookRepositoryTest {
  //...
}

For example, JPA tests using Testcontainers, and we don’t want the @DataJpaTest to replace the Testcontainers data source, we have to define the @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE).

BookRepositoryTest.java

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
public class BookRepositoryTest {

    // static, all tests share this postgres container
    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
            "postgres:15-alpine"
    );

    //...
}

4. Test Spring Data JPA application using @DataJpaTest

The below example uses @DataJpaTest for the Spring Data JPA setup and configuration and tests the BookRepository, reading the test codes for self-explanatory.

BookRepositoryTest.java

package com.mkyong;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @DataJpaTest
 * 1. It scans the `@Entity` classes and Spring Data JPA repositories.
 * 2. Set the `spring.jpa.show-sql` property to true and enable the SQL queries logging.
 * 3. Default, JPA test data are transactional and roll back at the end of each test;
 * it means we do not need to clean up saved or modified table data after each test.
 * 4. Replace the application DataSource, run and configure the embedded database on classpath.
 */
@DataJpaTest
public class BookRepositoryTest {

  // Alternative for EntityManager
  // Optional in this case, we can use bookRepository to do the same stuff
  @Autowired
  private TestEntityManager testEM;

  @Autowired
  private BookRepository bookRepository;

  @Test
  public void testSave() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));

      //testEM.persistAndFlush(b1); the same
      bookRepository.save(b1);

      Long savedBookID = b1.getId();

      Book book = bookRepository.findById(savedBookID).orElseThrow();
      // Book book = testEM.find(Book.class, savedBookID);

      assertEquals(savedBookID, book.getId());
      assertEquals("Book A", book.getTitle());
      assertEquals(BigDecimal.valueOf(9.99), book.getPrice());
      assertEquals(LocalDate.of(2023, 8, 31), book.getPublishDate());


  }

  @Test
  public void testUpdate() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));

      //testEM.persistAndFlush(b1);
      bookRepository.save(b1);

      // update price from 9.99 to 19.99
      b1.setPrice(BigDecimal.valueOf(19.99));

      // update
      bookRepository.save(b1);

      List<Book> result = bookRepository.findByTitle("Book A");

      assertEquals(1, result.size());

      Book book = result.get(0);
      assertNotNull(book.getId());
      assertTrue(book.getId() > 0);

      assertEquals("Book A", book.getTitle());
      assertEquals(BigDecimal.valueOf(19.99), book.getPrice());
      assertEquals(LocalDate.of(2023, 8, 31), book.getPublishDate());


  }

  @Test
  public void testFindByTitle() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));
      bookRepository.save(b1);

      List<Book> result = bookRepository.findByTitle("Book A");

      assertEquals(1, result.size());
      Book book = result.get(0);
      assertNotNull(book.getId());
      assertTrue(book.getId() > 0);

      assertEquals("Book A", book.getTitle());
      assertEquals(BigDecimal.valueOf(9.99), book.getPrice());
      assertEquals(LocalDate.of(2023, 8, 31), book.getPublishDate());

  }

  @Test
  public void testFindAll() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));
      Book b2 = new Book("Book B",
              BigDecimal.valueOf(19.99),
              LocalDate.of(2023, 7, 31));
      Book b3 = new Book("Book C",
              BigDecimal.valueOf(29.99),
              LocalDate.of(2023, 6, 10));
      Book b4 = new Book("Book D",
              BigDecimal.valueOf(39.99),
              LocalDate.of(2023, 5, 5));

      bookRepository.saveAll(List.of(b1, b2, b3, b4));

      List<Book> result = bookRepository.findAll();
      assertEquals(4, result.size());

  }

  @Test
  public void testFindByPublishedDateAfter() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));
      Book b2 = new Book("Book B",
              BigDecimal.valueOf(19.99),
              LocalDate.of(2023, 7, 31));
      Book b3 = new Book("Book C",
              BigDecimal.valueOf(29.99),
              LocalDate.of(2023, 6, 10));
      Book b4 = new Book("Book D",
              BigDecimal.valueOf(39.99),
              LocalDate.of(2023, 5, 5));

      bookRepository.saveAll(List.of(b1, b2, b3, b4));

      List<Book> result = bookRepository.findByPublishedDateAfter(
              LocalDate.of(2023, 7, 1));

      // b1 and b2
      assertEquals(2, result.size());

  }

  @Test
  public void testDeleteById() {

      Book b1 = new Book("Book A",
              BigDecimal.valueOf(9.99),
              LocalDate.of(2023, 8, 31));
      bookRepository.save(b1);

      Long savedBookID = b1.getId();

      // Book book = bookRepository.findById(savedBookID).orElseThrow();
      // Book book = testEM.find(Book.class, savedBookID);

      bookRepository.deleteById(savedBookID);

      Optional<Book> result = bookRepository.findById(savedBookID);
      assertTrue(result.isEmpty());

  }

}

tests passed

5. Download Source Code

$ git clone https://github.com/mkyong/spring-boot.git

$ cd spring-data-jpa-test

$ ./mvnw test

6. References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Mike
3 months ago

Great explanation! It’s easy to understand. Thanks!