Introduction
The @DataJpaTest
annotation in Spring Boot is specifically designed for isolated testing of JPA repositories. Instead of bootstrapping the entire Spring Boot application, it loads only JPA-related components such as:
✅ @Entity
classes
✅ Spring Data JPA repositories
✅ Database configuration
What Happens When Using @DataJpaTest
?
By default, @DataJpaTest
:
- Automatically configures an in-memory database (H2, HSQLDB, Derby) if available.
- Rolls back transactions at the end of each test, ensuring data isolation.
- Excludes unnecessary beans (
@Component
,@Service
,@Controller
), making tests lightweight and fast. - Executes schema and data SQL scripts when specified.
This approach, known as “slicing”, ensures faster execution and more focused testing.
Project Setup
To get started, create a Spring Boot project with the following dependencies:
- Spring Data JPA – For database interaction.
- H2 Database – An in-memory database for testing.
-
Postgresql – Will not be used for testing, we just imagine that we have this database in real env.
- Spring Boot Starter Test – Includes JUnit, AssertJ, and Mockito.
Spring Boot Testing Libraries
Spring Boot provides a comprehensive testing framework through the spring-boot-starter-test
dependency. It includes:
- JUnit – The core testing framework.
- Spring Test & Spring Boot Test – Utilities for integration and context-based testing.
- AssertJ – A fluent assertion library.
- Hamcrest – Advanced matcher library.
- Mockito – A powerful mocking framework.
- JSONassert – For JSON structure comparisons.
- JsonPath – For querying and asserting JSON responses.
Implementation
File: pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>tech.devblueprint</groupId> <artifactId>datajpatest-spring-boot-testing-example</artifactId> <version>0.0.1-SNAPSHOT</version> <name>datajpatest-spring-boot-testing-example</name> <description>Demo project for Spring Boot</description> <url/> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Key Dependencies Explained:
spring-boot-starter-data-jpa
– Provides JPA support for interacting with relational databases.com.h2database:h2
– Includes the H2 in-memory database for lightweight testing.org.postgresql:postgresql
– Adds support for PostgreSQL (useful when switching from H2 to a real database).spring-boot-starter-test
– Brings all essential testing libraries, such as JUnit, Mockito, and AssertJ.
H2 Database Configuration (Automatic)
One of the biggest advantages of @DataJpaTest
is that Spring Boot automatically configures an in-memory H2 database, so no manual configuration is needed.
There’s no need to create a application-test.yaml
file, as Spring Boot will:
- Use H2 as the default database (without additional setup).
- Automatically generate a fresh schema for each test.
- Roll back transactions after each test execution.
Entity Definition
File: src/main/java/.../entity/Device.java
package tech.devblueprint.datajpatest_spring_boot_testing_example.entity; import jakarta.persistence.*; @Entity @Table(name = "devices") public class Device { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String type; private Double price; public Device() { } public Device(String name, String type, Double price) { this.name = name; this.type = type; this.price = price; } // Getters и Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }
2. Repository Interface
File: src/main/java/.../repository/DeviceRepository.java
package tech.devblueprint.datajpatest_spring_boot_testing_example.repository; import tech.devblueprint.datajpatest_spring_boot_testing_example.entity.Device; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; public interface DeviceRepository extends JpaRepository<Device, Long> { // Derived Query List<Device> findByType(String type); // JPQL-Query @Query("FROM Device d WHERE d.price > ?1") List<Device> findDevicesByPriceGreaterThan(Double price); // Native SQL-Query @Query(value = "SELECT * FROM devices WHERE name = :name", nativeQuery = true) Optional<Device> findByName(String name); }
Writing Tests with H2 Database
We will now create a test class that:
- Uses
@DataJpaTest
for repository testing. - Runs in-memory H2 database instead of a real database.
- Loads test data from SQL scripts.
File: src/test/java/.../repository/DeviceRepositoryTest.java
package tech.devblueprint.datajpatest_spring_boot_testing_example.repository; 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.test.context.jdbc.Sql; import tech.devblueprint.datajpatest_spring_boot_testing_example.entity.Device; import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @Sql({"/test-device-schema.sql"}) // Execute the schema creation script public class DeviceRepositoryTest { @Autowired private DeviceRepository deviceRepository; // Tests for the findByName method // Successful search for device by name "iPhone 13" @Test @Sql({"/test-device-data.sql"}) public void testFindByName_ReturnsDevice() { Optional<Device> deviceOpt = deviceRepository.findByName("iPhone 13"); assertThat(deviceOpt).isPresent(); Device device = deviceOpt.get(); assertThat(device.getName()).isEqualTo("iPhone 13"); assertThat(device.getType()).isEqualTo("Smartphone"); assertThat(device.getPrice()).isEqualTo(999.99); } // Search for a non-existent device returns Optional.empty() @Test @Sql({"/test-device-data.sql"}) public void testFindByName_NotFound() { Optional<Device> deviceOpt = deviceRepository.findByName("Non Existent Device"); assertThat(deviceOpt).isNotPresent(); } // Case sensitivity test (expecting that the search is case-sensitive) @Test @Sql({"/test-device-data.sql"}) public void testFindByName_CaseSensitive() { Optional<Device> deviceOpt = deviceRepository.findByName("iphone 13"); // Expecting no result due to case sensitivity assertThat(deviceOpt).isNotPresent(); } // Tests for the findDevicesByPriceGreaterThan method // When the threshold is below the minimum price, all devices should be returned @Test @Sql({"/test-device-data.sql"}) public void testFindDevicesByPriceGreaterThan_AllDevices() { List<Device> devices = deviceRepository.findDevicesByPriceGreaterThan(0.0); // There are 4 devices in the test data assertThat(devices).hasSize(4); } // Search for devices with price greater than 1000 (expecting two devices: Dell XPS 13 and MacBook Pro) @Test @Sql({"/test-device-data.sql"}) public void testFindDevicesByPriceGreaterThan_TwoDevices() { List<Device> devices = deviceRepository.findDevicesByPriceGreaterThan(1000.0); assertThat(devices).hasSize(2); assertThat(devices) .extracting(Device::getName) .containsExactlyInAnyOrder("Dell XPS 13", "MacBook Pro"); } // When the threshold exceeds the maximum price, the result list should be empty @Test @Sql({"/test-device-data.sql"}) public void testFindDevicesByPriceGreaterThan_NoDevices() { List<Device> devices = deviceRepository.findDevicesByPriceGreaterThan(3000.0); assertThat(devices).isEmpty(); } // Tests for the findByType method // Search for devices of type "Laptop" should return two devices @Test @Sql({"/test-device-data.sql"}) public void testFindByType_ReturnsLaptops() { List<Device> devices = deviceRepository.findByType("Laptop"); assertThat(devices).hasSize(2); assertThat(devices) .extracting(Device::getName) .containsExactlyInAnyOrder("Dell XPS 13", "MacBook Pro"); } // Search for devices of type "Smartphone" should return two devices @Test @Sql({"/test-device-data.sql"}) public void testFindByType_ReturnsSmartphones() { List<Device> devices = deviceRepository.findByType("Smartphone"); assertThat(devices).hasSize(2); assertThat(devices) .extracting(Device::getName) .containsExactlyInAnyOrder("iPhone 13", "Galaxy S22"); } // Search for devices of a non-existent type (e.g., "Tablet") should return an empty list @Test @Sql({"/test-device-data.sql"}) public void testFindByType_NotFound() { List<Device> devices = deviceRepository.findByType("Tablet"); assertThat(devices).isEmpty(); } }
Understanding Key Annotations
@DataJpaTest
– Loads only JPA components for testing.@Sql("/test-device-schema.sql")
– Runs schema creation script before tests.@Sql("/test-device-data.sql")
– Loads test data before each test.
Understanding Key Annotations
@DataJpaTest
– Loads only JPA-related components.@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
– Prevents Spring Boot from replacing PostgreSQL with H2.@Sql("/test-device-schema.sql")
– Runs a schema creation script before tests.@Sql("/test-device-data.sql")
– Loads test data before each test.
Understanding Transactions in @DataJpaTest
By default, tests annotated with @DataJpaTest
run in transactions and automatically roll back at the end of each test. This ensures that each test starts with a clean database state.
However, if you need to disable transaction management, use @Transactional(propagation = Propagation.NOT_SUPPORTED)
, as shown below:
@DataJpaTest @Transactional(propagation = Propagation.NOT_SUPPORTED) public class StudentRepositoryTests { @Autowired private StudentRepository studentRepository; @Test void findByName_ReturnsTheStudent() { // Test logic... } }
This is useful when testing database operations that require commits, such as stored procedures or triggers.
Example SQL Scripts
SQL scripts should be placed in src/test/resources
so that Spring Boot can automatically load them during test execution.
Schema Definition
File: src/test/resources/test-device-schema.sql
CREATE TABLE devices ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, type VARCHAR(100), price DOUBLE );
Test Data Insertion
File: src/test/resources/test-device-data.sql
INSERT INTO devices (id, name, type, price) VALUES (1, 'iPhone 13', 'Smartphone', 999.99), (2, 'Galaxy S22', 'Smartphone', 899.99), (3, 'Dell XPS 13', 'Laptop', 1199.99), (4, 'MacBook Pro', 'Laptop', 1999.99);
Conclusion
In this guide, we explored how to use @DataJpaTest
with H2 in-memory database instead of a real PostgreSQL database.