Spring Boot @DataJpaTest
last modified July 28, 2023
In this article we show 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. 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.
build.gradle
...
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.
plugins {
id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
}
group = 'com.zetcode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.h2database:h2'
}
test {
useJUnitPlatform()
}
The Gradle build file contains dependencies for Spring Data JPA, testing, and H2 database.
spring.main.banner-mode=off spring.sql.init.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.sql.init.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 jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.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.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static org.assertj.core.api.Assertions.assertThat;
@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.
@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.
$ ./gradlew bootRun
We run the tests.
In this article we have showed how to test a custom JPA repository method
utilizing @DataJpaTest.