Through this tutorial, you will learn how to write unit tests for a Spring Boot project that uses Spring Data JPA and Hibernate for the data access layer. I will also share with you how I write code for testing CRUD operations of a Spring Data JPA repository.

I will be using JUnit 5 (JUnit Jupiter) in a Spring Boot project with Spring Data JPA, Hibernate and MySQL database.

 

1. Annotations for Unit Testing Spring Data JPA

When coding the data access layer, you can test only the Spring Data JPA repositories without testing a fully loaded Spring Boot application.

Spring Boot provides the following annotations which you can use for unit testing JPA repositories:

 

@DataJpaTest:

This annotation will disable full auto-configuration and instead apply only configuration relevant to JPA tests. By default, it will use an embedded, in-memory H2 database instead of the one declared in the configuration file, for faster test running time as compared to disk file database.

Let’s see an example test class:

package net.codejava;

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;

@DataJpaTest
public class ProductRepositoryTests {
	@Autowired
	private TestEntityManager entityManager;
	
	@Autowired
	private ProductRepository repository;
	
	@Test
	public void testSaveNewProduct() {
		entityManager.persist(new Product("iPhone 10", 1099));
				
		Product product = repository.findByName("iPhone 10");
		
		assertThat(product.getName()).isEqualTo("iPhone 10");
	}
}

As you can see, in this test class we can inject a TestEntityManager and ProductRepository. TestEntityManager is a subset of JPA EntityManager. It allows us to quickly test JPA without the need to manually configure/instantiating an EntityManagerFactory and EntityManager.

And ProductRepository is the repository that need to be tested, along with the entity class Product. The repository proxy class generated by Spring Data JPA is very well implemented, so it’s for testing the entity classes and custom methods in repository interfaces.

You also see I use assertion method from AssertJ for more readable, meaningful assertion statements – instead of JUnit’s ones:

assertThat(product.getName()).isEqualTo("iPhone 10");

  

@Rollback:

Note that by default, tests using @DataJpaTest are transactional and roll back at the end of each test method. If you want to disable auto rollback for the whole test class, annotate the class with the @Rollback annotation as follows:

@DataJpaTest
@Rollback(false)
public class ProductRepositoryTests {
	...
}

Or you can selectively disable roll back at method level. For example:

@DataJpaTest
public class ProductRepositoryTests {
	@Autowired
	private TestEntityManager entityManager;
	
	@Autowired
	private ProductRepository repository;
	
	@Test
	@Rollback(false)
	public void testSaveNewProduct() {
		...
	}
	
	@Test	
	public void testUpdateProduct() {
		...
	}
}

Disabling roll back for tests will be useful when a test method depends on the data of others.

 

@AutoConfigureTestDatabase:

By default, the @DataJpaTest annotation replaces the declared database configuration by an in-memory database (H2), which is useful when running tests that doesn’t touch real database. When you want to run tests on real database, use the @AutoConfigureTestDatabase as follows:

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class ProductRepositoryTests {
	...
}

Then Spring Boot will use the data source declared in the application configuration file.

Next, let’s go through a sample Spring Boot project that uses unit tests for Spring Data JPA.

 

2. Required Dependencies

If you create a Spring Boot project using Spring Tool Suite IDE or directly from Spring Initializr, the dependency spring boot starter test is included by default. And we need to add dependencies for the in-memory database (H2) and real database (MySQL).

So make sure to declare the following dependencies in the Maven’s project file:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
		</exclusion>
	</exclusions>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

Note that we exclude JUnit Vintage which supports running JUnit 4’s tests. Since we will write tests using JUnit 5, JUnit Vintage is no needed.

 

3. Code Entity Class

Create an entity class named Product with the following code:

package net.codejava;

import javax.persistence.*;

@Entity
@Table(name = "products")
public class Product {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(length = 64, unique = true, nullable = false)
	private String name;
	
	private float price;
	
	public Product(String name, float price) {
		this.name = name;
		this.price = price;
	}

	public Product() {
	}

	// getters and setters are not shown for brevity...

	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + ", price=" + price + "]";
	}		
}

As you can see, this is a simple entity class with only 3 fields: id, name and price. The getters and setters are not shown for brevity. We will use Hibernate forward engineering to create the corresponding table in the database when running tests.

 

4. Code Repository Interface

Because we use Spring Data JPA with Hibernate, so code the ProductRepository interface as follows:

package net.codejava;

import org.springframework.data.repository.CrudRepository;

public interface ProductRepository extends CrudRepository<Product, Integer> {
	
	public Product findByName(String name);
}

Besides the default CRUD methods extended from CrudRepository, we declare a custom method findByName() – and by convention, this method will return a Product object by its name. Read this tutorial if you’re new to Spring Data JPA.

 

5. Configure database connection properties

Next, open the Spring Boot configuration file (application.properties) and specify the properties for database connection as follows:

spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format-sql=true

Make sure that you created the database schema named test in MySQL server. Note that we specify create value for the spring.jpa.hibernate.ddl-auto property, so Hibernate will create the database table upon startup. And it will drop the table if exists.

 

6. Code Tests for CRUD operations

Create the ProductRepositoryTests class under src/test/java directory with initial code as follows:

package net.codejava;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

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

	@Autowired
	private ProductRepository repo;
	
	// test methods for CRUD operations...
}

As you can see, this class will run tests with the actual database.

And now, let’s write test methods for testing CRUD operations of ProductRepository.

 

Test Create operation:

The first one is for testing saving a new product:

@Test
@Rollback(false)
public void testCreateProduct() {
	Product savedProduct = repo.save(new Product("iPhone 10", 789));
	
	assertThat(savedProduct.getId()).isGreaterThan(0);
}

I use @Rollback(false) to disable roll back to the data will be committed to the database and available for the next test methods which will run separately. And I use assertThat() method from AssertJ library for more readability than using JUnit’s assertion methods. So you need to add the following imports:

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.test.annotation.Rollback;

Run this first test method, and you will see Hibernate prints the following output:

Hibernate: drop table if exists products
Hibernate: create table products...
Hibernate: alter table products add constraint...
Hibernate: insert into products (name, price) values (?, ?)

 

Test Retrieval Operation:

Before writing the second test, set the property spring.jpa.hibernate.ddl-auto to none so Hibernate won’t drop the table in the next run. We want to keep data for the subsequence tests.

Code the second test method to test finding a product by name, as follows:

@Test
public void testFindProductByName() {
	Product product = repo.findByName("iPhone 10");		
	assertThat(product.getName()).isEqualTo("iPhone 10");
}

Run this test method, and you will see Hibernate prints the SELECT SQL statement as follows:

Hibernate: select product0_.id as... from products product0_ where product0_.name=?

Next, write the third test method to test retrieving all products, with the following code:

@Test
public void testListProducts() {
	List<Product> products = (List<Product>) repo.findAll();
	assertThat(products).size().isGreaterThan(0);
}

Run this test, and you will see Hibernate prints the following SQL statement:

Hibernate: select product0_.id as... from products product0_

  

Test Update Operation:

Next, let’s code the fourth test method to test updating a product, with the following code:

@Test
@Rollback(false)
public void testUpdateProduct() {
	Product product = repo.findByName("iPhone 10");
	product.setPrice(1000);
	
	repo.save(product);
	
	Product updatedProduct = repo.findByName("iPhone 10");
	
	assertThat(updatedProduct.getPrice()).isEqualTo(1000);
}

As you can see, first we get the product by its name, then update its price. Then we get the product again, and compare the price. Run this test, you will see Hibernate prints the following SQL statements:

Hibernate: select product0_.id as... from products product0_ where product0_.name=?
Hibernate: update products set name=?, price=? where id=?
Hibernate: select product0_.id as... from products product0_ where product0_.name=?

  

Test Delete Operation:

The last test method is for testing delete a product. Code this method as follows:

@Test
@Rollback(false)
public void testDeleteProduct() {
	Product product = repo.findByName("iPhone 10");
	
	repo.deleteById(product.getId());
	
	Product deletedProduct = repo.findByName("iPhone 10");
	
	assertThat(deletedProduct).isNull();		
	
}

Run this test and you will see Hibernate prints the following SQL statements:

Hibernate: select product0_.id as... from products product0_ where product0_.name=?
Hibernate: delete from products where id=?
Hibernate: select product0_.id as... from products product0_ where product0_.name=?

So that we’ve coded 5 test methods for testing CRUD operations of a Spring Data JPA repository. Now we can run all these tests methods at once. But we need to specify the execution order because JUnit doesn’t run test methods in the order they appear in the code. So we need to use the @TestMethodOrder and @Order annotations as follows:

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@TestMethodOrder(OrderAnnotation.class)
public class ProductRepositoryTests {

	@Autowired
	private ProductRepository repo;
	
	@Test
	@Rollback(false)
	@Order(1)
	public void testCreateProduct() {
		...
	}
	
	@Test
	@Order(2)
	public void testFindProductByName() {
		...
	}
	
	@Test
	@Order(3)
	public void testListProducts() {
		...
	}
	
	@Test
	@Rollback(false)
	@Order(4)
	public void testUpdateProduct() {
		...
	}
	
	@Test
	@Rollback(false)
	@Order(5)
	public void testDeleteProduct() {
		...
	}
}

With this update, now we can run the whole test class with in-memory database and/or with create-drop setting.

 

Conclusion

So far you have learned how to write unit tests for testing CRUD operations for a repository with Spring Data JPA and Hibernate framework. You see Spring and JUnit make it’s easy to write the tests, and AssertJ makes the assertion much more readable. I hope you found this tutorial helpful. You can watch the video version below and download the sample project under the Attachments section.

 

References:

 

Related Tutorials:

 

Other Spring Boot Tutorials:

 


About the Author:

is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.



Attachments:
Download this file (ProductAppUnitTests.zip)ProductAppUnitTests.zip[Sample Spring Boot project]70 kB

Add comment