Spring Boot @DataJpaTest
last modified May 10, 2022
Spring Boot @DataJpaTest tutorial shows how to test JPA repositories using @DataJpaTest annotation.
Spring is a popular Java application framework for creating enterprise applications. Spring Boot is an evolution of Spring framework which helps create stand-alone, production-grade Spring based applications with minimal effort.
@DataJpaTest
@DataJpaTest
is used to test JPA repositories. It is used in combination
with @RunWith(SpringRunner.class)
. The annotation disables full
auto-configuration and applies only configuration relevant to JPA tests.
By default, tests annotated with @DataJpaTest
use an embedded in-memory database.
In our tests, we can inject a DataSource, @JdbcTemplate, @EntityManager or any Spring Data repository from our application.
The application context containing all these components, including the in-memory
database, is shared between all test methods within all test classes annotated
with @DataJpaTest
. Therefore, each test method runs in its own
transaction, which is rolled back after the method has executed. This way the
tests stay independent from each other.
Spring @DataJpaTest example
The following application creates a custom JPA query method. The method is
tested in a test class annotated with @DataJpaTest
.
pom.xml src ├───main │ ├───java │ │ └───com │ │ └───zetcode │ │ │ Application.java │ │ │ MyRunner.java │ │ ├───model │ │ │ City.java │ │ └───repository │ │ CityRepository.java │ └───resources │ application.properties │ data-h2.sql │ schema-h2.sql └───test └───java └───com └───zetcode └───repository CityRepositoryTest.java
This is the project structure.
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>springbootdatajpatest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.7</version> </plugin> </plugins> </build> </project>
The Maven POM file contains dependencies for Spring Data JPA, testing, and H2 database.
spring.main.banner-mode=off spring.datasource.platform=h2 spring.jpa.hibernate.ddl-auto=none
The application.properties
is the main Spring Boot configuration
file. With the spring.main.banner-mode
property we turn off the
Spring banner. The spring.datasource.platform
sets the vendor name of the database. It is used in the initialization scripts.
Finally, the spring.jpa.hibernate.ddl-auto
disables the automatic
creation of schemas from entities.
CREATE TABLE cities(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), population INT);
When the application is started, the schema-h2.sql
script
is executed. It creates a new database table.
INSERT INTO cities(name, population) VALUES('Bratislava', 432000); INSERT INTO cities(name, population) VALUES('Budapest', 1759000); INSERT INTO cities(name, population) VALUES('Prague', 1280000); INSERT INTO cities(name, population) VALUES('Warsaw', 1748000); INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000); INSERT INTO cities(name, population) VALUES('New York', 8550000); INSERT INTO cities(name, population) VALUES('Edinburgh', 464000); INSERT INTO cities(name, population) VALUES('Suzhou', 4327066); INSERT INTO cities(name, population) VALUES('Zhengzhou', 4122087); INSERT INTO cities(name, population) VALUES('Berlin', 3671000); INSERT INTO cities(name, population) VALUES('Brest', 139163); INSERT INTO cities(name, population) VALUES('Bucharest', 1836000);
Later, the data-h2.sql
file is executed. It fills the
table with data.
package com.zetcode.model; import java.util.Objects; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "cities") public class City { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int population; public City() { } public City(String name, int population) { this.name = name; this.population = population; } 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 int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public int hashCode() { int hash = 7; hash = 79 * hash + Objects.hashCode(this.id); hash = 79 * hash + Objects.hashCode(this.name); hash = 79 * hash + this.population; return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final City other = (City) obj; if (this.population != other.population) { return false; } if (!Objects.equals(this.name, other.name)) { return false; } return Objects.equals(this.id, other.id); } @Override public String toString() { var builder = new StringBuilder(); builder.append("City{id=").append(id).append(", name=") .append(name).append(", population=") .append(population).append("}"); return builder.toString(); } }
This is the City
entity.
package com.zetcode.repository; import com.zetcode.model.City; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface CityRepository extends CrudRepository<City, Long> { @Query("SELECT c FROM City c WHERE c.name LIKE CONCAT('%',:ending, '%') AND c.population < :num") List<City> findByNameEndingWithAndPopulationLessThan(@Param("ending") String ending, @Param("num") Integer num); }
CityRepository
contains the custom findByNameEndingWithAndPopulationLessThan()
method. With the method we get all city names that end with the specified string and their population
is lower than the specified value.
package com.zetcode; import com.zetcode.repository.CityRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class MyRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(MyRunner.class); @Autowired private CityRepository cityRepository; @Override public void run(String... args) throws Exception { var cities = cityRepository.findByNameEndingWithAndPopulationLessThan("est", 1800000); cities.forEach(city -> logger.info("{}", city)); } }
In MyRunner
we use the findByNameEndingWithAndPopulationLessThan()
method.
package com.zetcode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
The Application
sets up the Spring Boot application.
The @SpringBootApplication
enables auto-configuration and
component scanning.
package com.zetcode.repository; import com.zetcode.model.City; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @DataJpaTest public class CityRepositoryTest { @Autowired private CityRepository repository; @Test public void should_find_all_customers() { Iterable<City> cities = repository.findAll(); int nOfCities = 12; assertThat(cities).hasSize(nOfCities); } @Test public void should_find_with_name_ending_population_less_than() { var cities = repository.findByNameEndingWithAndPopulationLessThan("est", 150000); assertThat(cities).isNotEmpty(); } }
In CityRepositoryTest
, we test the custom JPA method.
@RunWith(SpringRunner.class) @DataJpaTest public class CityRepositoryTest {
The CityRepositoryTest
is annotated with @DataJpaTest
.
The in-memory H2 database is used to perform the integration tests.
@Test public void should_find_with_name_ending_population_less_than() { var cities = repository.findByNameEndingWithAndPopulationLessThan("est", 150000); assertThat(cities).isNotEmpty(); }
This method tests that there is at least one city with name ending in 'est' and with population less than 150000.
$ mvn spring-boot:test
We run the tests.
In this tutorial, we have showed how to test a custom JPA repository method utilizing
@DataJpaTest
.