@DataJpaTest example for Spring Data Repository Unit Test

Nowadays Unit Test is so important in Software Development, and Spring Framework also provides @DataJpaTest annotation to make writing test for JPA Repository more simpler. In this tutorial, we’re gonna look at how to apply @DataJpaTest in our Spring Boot Project with TestEntityManager, then run with JUnit 5.

This tutorial gives you an additional unit test for the Post:
Spring Boot, Spring Data JPA: CRUD example

More Practice:
Spring Boot – test Rest Controller with @WebMvcTest
JPA Repository query example in Spring Boot
Spring Boot Token based Authentication with Spring Security example
Spring Boot @ControllerAdvice & @ExceptionHandler example
– Documentation: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
– Validation: Spring Boot Validate Request Body

Associations:
Spring Boot One To One example with JPA, Hibernate
Spring Boot One To Many example with JPA, Hibernate
Spring Boot Many to Many example with JPA, Hibernate


Spring Boot @DataJpaTest example Overview

We have Tutorial model with some fields: id, title, description, published.
There is a repository to interact with Tutorials from the database called TutorialRepository interface that extends JpaRepository:

public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
  List<Tutorial> findByPublished(boolean published);
  List<Tutorial> findByTitleContaining(String title);
}

JpaRepository also supports following methods: save(), findOne(), findById(), findAll(), count(), delete(), deleteById().

So how to write unit test for all of them?
=> We’re gonna use @DataJpaTest with TestEntityManager.

@RunWith(SpringRunner.class)
@DataJpaTest
public class JPAUnitTest {

  @Autowired
  private TestEntityManager entityManager;

  @Autowired
  TutorialRepository repository;

  @Test
  public void should_find_no_tutorials_if_repository_is_empty() { }

  @Test
  public void should_store_a_tutorial() { }

  @Test
  public void should_find_all_tutorials() { }

  @Test
  public void should_find_tutorial_by_id() { }

  @Test
  public void should_find_published_tutorials() { }

  @Test
  public void should_find_tutorials_by_title_containing_string() { }

  @Test
  public void should_update_tutorial_by_id() { }

  @Test
  public void should_delete_tutorial_by_id() { }

  @Test
  public void should_delete_all_tutorials() { }
}

For testing, we’ll work with H2 in-memory database. It eliminates the need for configuring and starting an actual database.

@DataJpaTest annotation for testing JPA Repository

@DataJpaTest is the annotation that Spring supports for a JPA test that focuses only on JPA components.

It will disable full auto-configuration and then, apply only enable configuration relevant to JPA tests. The list of the auto-configuration settings that are enabled can be found here.

By default, tests annotated with @DataJpaTest are transactional and roll back at the end of each test. If you don’t want it, you can disable transaction management for a test or for the whole class using @Transactional annotation:

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class YourNonTransactionalTests {

}

In-memory embedded database (like H2 database in this example) generally works well for tests, it is fast and does not require any installation. However, we can configure for a real database with @AutoConfigureTestDatabase annotation:

@DataJpaTest
@AutoConfigureTestDatabase(replace=Replace.NONE)
class YourRepositoryTests {

}

If you are using JUnit 4, you need to add @RunWith(SpringRunner.class) to the test:

@RunWith(SpringRunner.class)
@DataJpaTest
class YourRepositoryTests {

}

TestEntityManager

The purpose of the EntityManager is to interact with the persistence context. Spring Data JPA abstractes you from the EntityManager through Repository interfaces. And TestEntityManager allows us to use EntityManager in tests.

We can inject a TestEntityManager bean in Data JPA tests. If you want to use TestEntityManager outside of @DataJpaTest instances, just add @AutoConfigureTestEntityManager annotation.

The following example shows the @DataJpaTest annotation with TestEntityManager:

@DataJpaTest
class YourRepositoryTests {

  @Autowired
  private TestEntityManager entityManager;

  @Test
  void testExample() throws Exception {
    this.entityManager.persist(new Tutorial("Tut#1", "Desc#1", true));
    ...
  }
}

Project Structure

Let’s look at our Spring Boot project:

spring-boot-datajpatest-jpa-repository-unit-test-project-structure

Tutorial data model class corresponds to entity and table tutorials.
TutorialRepository is an interface that extends JpaRepository for CRUD methods and custom finder methods. It will be autowired in JPAUnitTest.
JPAUnitTest is the main Test Class used for testing JPA and annotated with @DataJpaTest.
– pom.xml contains dependencies for Spring Boot, JPA, H2 database.

Setup Spring Boot @DataJpaTest Project

Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.

Then open pom.xml and add these 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>test</scope>
</dependency>

Define Data Model

In model package, we define Tutorial class.
Our Data model (entity) contains 4 fields: id, title, description, published.

model/Tutorial.java

package com.bezkoder.spring.data.jpa.test.model;

import javax.persistence.*;
// import jakarta.persistence.*; // for Spring Boot 3

@Entity
@Table(name = "tutorials")
public class Tutorial {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	@Column(name = "title")
	private String title;

	@Column(name = "description")
	private String description;

	@Column(name = "published")
	private boolean published;

	public Tutorial() {

	}

	public Tutorial(String title, String description, boolean published) {
		this.title = title;
		this.description = description;
		this.published = published;
	}

	public long getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isPublished() {
		return published;
	}

	public void setPublished(boolean isPublished) {
		this.published = isPublished;
	}

	@Override
	public String toString() {
		return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
	}
}

@Entity annotation indicates that the class is a persistent Java class.
@Table annotation provides the table that maps this entity.
@Id annotation is for the primary key.
@GeneratedValue annotation is used to define generation strategy for the primary key. GenerationType.AUTO means Auto Increment field.
@Column annotation is used to define the column in database that maps annotated field.

Create JPA Repository

In repository package, create TutorialRepository interface that extends JpaRepository. This repository will interact with Tutorials from the database.

repository/TutorialRepository.java

package com.bezkoder.spring.data.jpa.test.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bezkoder.spring.data.jpa.test.model.Tutorial;

public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
  List<Tutorial> findByPublished(boolean published);

  List<Tutorial> findByTitleContaining(String title);
}

Now we can use JpaRepository’s methods: save(), findOne(), findById(), findAll(), count(), delete(), deleteById()… without implementing these methods.

We also define custom finder methods:
findByPublished(): returns all Tutorials with published having value as input published.
findByTitleContaining(): returns all Tutorials which title contains input title.

The implementation is plugged in by Spring Data JPA automatically.

More Derived queries at:
JPA Repository query example in Spring Boot

Custom query with @Query annotation:
Spring JPA @Query example: Custom query in Spring Boot

You can also modify this Repository to work with Pagination, the instruction can be found at:
Spring Boot Pagination & Filter example | Spring JPA, Pageable

Write Unit Test With @DataJpaTest

Under src/test/java, create a class named JPAUnitTest that extends. We’re gonna test many cases (CRUD operations, finder methods) inside this class.

package com.bezkoder.spring.data.jpa.test;

import static org.assertj.core.api.Assertions.assertThat;

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 com.bezkoder.spring.data.jpa.test.model.Tutorial;
import com.bezkoder.spring.data.jpa.test.repository.TutorialRepository;

@DataJpaTest
public class JPAUnitTest {

  @Autowired
  private TestEntityManager entityManager;

  @Autowired
  TutorialRepository repository;

  @Test
  public void should_find_no_tutorials_if_repository_is_empty() {
    Iterable tutorials = repository.findAll();

    assertThat(tutorials).isEmpty();
  }

  @Test
  public void should_store_a_tutorial() {
    Tutorial tutorial = repository.save(new Tutorial("Tut title", "Tut desc", true));

    assertThat(tutorial).hasFieldOrPropertyWithValue("title", "Tut title");
    assertThat(tutorial).hasFieldOrPropertyWithValue("description", "Tut desc");
    assertThat(tutorial).hasFieldOrPropertyWithValue("published", true);
  }

  @Test
  public void should_find_all_tutorials() {
    Tutorial tut1 = new Tutorial("Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial tut3 = new Tutorial("Tut#3", "Desc#3", true);
    entityManager.persist(tut3);

    Iterable tutorials = repository.findAll();

    assertThat(tutorials).hasSize(3).contains(tut1, tut2, tut3);
  }

  @Test
  public void should_find_tutorial_by_id() {
    Tutorial tut1 = new Tutorial("Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial foundTutorial = repository.findById(tut2.getId()).get();

    assertThat(foundTutorial).isEqualTo(tut2);
  }

  @Test
  public void should_find_published_tutorials() {
    Tutorial tut1 = new Tutorial("Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial tut3 = new Tutorial("Tut#3", "Desc#3", true);
    entityManager.persist(tut3);

    Iterable tutorials = repository.findByPublished(true);

    assertThat(tutorials).hasSize(2).contains(tut1, tut3);
  }

  @Test
  public void should_find_tutorials_by_title_containing_string() {
    Tutorial tut1 = new Tutorial("Spring Boot Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Java Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial tut3 = new Tutorial("Spring Data JPA Tut#3", "Desc#3", true);
    entityManager.persist(tut3);

    Iterable tutorials = repository.findByTitleContaining("ring");

    assertThat(tutorials).hasSize(2).contains(tut1, tut3);
  }

  @Test
  public void should_update_tutorial_by_id() {
    Tutorial tut1 = new Tutorial("Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial updatedTut = new Tutorial("updated Tut#2", "updated Desc#2", true);

    Tutorial tut = repository.findById(tut2.getId()).get();
    tut.setTitle(updatedTut.getTitle());
    tut.setDescription(updatedTut.getDescription());
    tut.setPublished(updatedTut.isPublished());
    repository.save(tut);

    Tutorial checkTut = repository.findById(tut2.getId()).get();
    
    assertThat(checkTut.getId()).isEqualTo(tut2.getId());
    assertThat(checkTut.getTitle()).isEqualTo(updatedTut.getTitle());
    assertThat(checkTut.getDescription()).isEqualTo(updatedTut.getDescription());
    assertThat(checkTut.isPublished()).isEqualTo(updatedTut.isPublished());
  }

  @Test
  public void should_delete_tutorial_by_id() {
    Tutorial tut1 = new Tutorial("Tut#1", "Desc#1", true);
    entityManager.persist(tut1);

    Tutorial tut2 = new Tutorial("Tut#2", "Desc#2", false);
    entityManager.persist(tut2);

    Tutorial tut3 = new Tutorial("Tut#3", "Desc#3", true);
    entityManager.persist(tut3);

    repository.deleteById(tut2.getId());

    Iterable tutorials = repository.findAll();

    assertThat(tutorials).hasSize(2).contains(tut1, tut3);
  }

  @Test
  public void should_delete_all_tutorials() {
    entityManager.persist(new Tutorial("Tut#1", "Desc#1", true));
    entityManager.persist(new Tutorial("Tut#2", "Desc#2", false));

    repository.deleteAll();

    assertThat(repository.findAll()).isEmpty();
  }
}

Run Unit Test

– First run command: mvn clean install.
– Then run Test: mvn test

[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.901 s - in com.bezkoder.spr
ing.data.jpa.test.JPAUnitTest
...
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  32.594 s
[INFO] Finished at: 2020-05-07T20:48:47+07:00
[INFO] ------------------------------------------------------------------------

– Or you can also run Spring Boot project with mode JUnit Test.
– Now see the Junit result as following:

spring-boot-datajpatest-jpa-repository-unit-test-result

Conclusion

Today we’ve create Spring Boot Test for JPA Repository with JUnit 5 using @DataJPATest and TestEntityManager with H2 Database. We also run unit test for many CRUD operations and custom finder methods.

You may need to run unit test for Rest Controller with:
Spring Boot Rest Controller Unit Test

Or handle Exception with:
Spring Boot @ControllerAdvice & @ExceptionHandler example

Happy learning! See you again.

Further Reading

Source Code

You can find the complete source code for this tutorial on Github.

You can use the code as an additional unit test for following Posts:
Spring Boot, Spring Data JPA, H2 example
Spring Boot, Spring Data JPA, MySQL example
Spring Boot, Spring Data JPA, PostgreSQL example
Spring Boot, Spring Data JPA, SQL Server example
Spring Boot, Spring Data JPA, Oracle example

Associations:
Spring Boot One To One example with JPA, Hibernate
Spring Boot One To Many example with JPA, Hibernate
Spring Boot Many to Many example with JPA, Hibernate

Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example
Validation: Spring Boot Validate Request Body

5 thoughts to “@DataJpaTest example for Spring Data Repository Unit Test”

  1. Thanks man, the @AutoConfigureTestDatabase(replace = Replace.NONE) tip saved me! Good job 🙂

  2. Why do we have repository and a TestEntityManager in the test?
    I see we use both to save the same entity, what the difference?

    1. Hi,

      1. When you want to check Repository’s save() method, you must use it.
      2. When you want to check other methods of Repository (not including save()), you must have data in database table, and you are NOT sure that Repository’s save() method is good or not, then you use TestEntityManager for persisting data.

  3. Hi,
    You are using @Autowired on fields, but I thought field injection was not recommended.

    Any comments ?

Comments are closed to reduce spam. If you have any question, please send me an email.