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
- 2. Spring Data JPA – Entity and Repository
- 3. @DataJpaTest
- 4. Test Spring Data JPA application using @DataJpaTest
- 5. Download Source Code
- 6. References
1. 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.
<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.
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
.
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.
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:
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.
// 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.
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.
@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)
.
@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"
);
//...
}
Further Reading
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.
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());
}
}
5. Download Source Code
6. References
- @DataJpaTest docs
- TestEntityManager docs
- H2 in-memory database
- HikariCP
- Spring Data JPA
- Spring Data JPA docs
- Common application properties
- JUnit – How to test a List
- Spring Boot + Spring data JPA example
- Spring Data JPA and PostgreSQL
- Spring Data JPA and Oracle example
- Testing JSON in Spring Boot
- Spring @TestPropertySource example
Great explanation! It’s easy to understand. Thanks!