Spring Boot - Testing Spring Data JDBC application with @DataJdbcTest - Example

In this section, we will learn how to test Repository layer components with @DataJdbcTest in Spring Boot application.


1. @DataJdbcTest

Instead of bootstrapping the entire application context for every test, @DataJdbcTest allows us to initialize the Spring application context with only those beans needed to test Spring Data JDBC-based components. It will auto-configure JdbcTemplate and if an embedded database is available on the classpath, @DataJdbcTest will autoconfigure one for testing purposes.

By default, tests annotated with @DataJdbcTest are transactional and roll back at the end of each test, means we do not need to clean up saved or modified table data after each test.

Regular @Component, @Service or @Controller beans are not scanned when using this annotation. 

This approach not only speeds up the testing process but also ensures a focused and efficient testing environment.

This approach is also known as "slicing" the application context.

Find the sample code snippet to use @DataJdbcTest annotation in unit test class

@DataJdbcTest
public class StudentRepositoryTests {

@Autowired
private StudentRepository studentRepository;

@Test
void findByName_ReturnsTheStudent() {

//todo...
}
}

When using JUnit 4, this annotation should be used in combination with @RunWith(SpringRunner.class). But for this example  we are using JUnit 5, there’s no need to add the equivalent @ExtendWith(SpringExtension.class).


2. Transactions

By default, tests annotated with @DataJdbcTest are transactional and roll back at the end of each test. We can disable transaction management for a test, use Propagation.NOT_SUPPORTED.

@DataJdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class StudentRepositoryTests {

@Autowired
private StudentRepository studentRepository;

@Test
void findByName_ReturnsTheStudent() {

//todo...
}
}


3. Test Against a Real Database

If you want run the tests against an application configured real database, use Replace.NONE.

@DataJdbcTest
@AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
public class StudentRepositoryTests {

@Autowired
private StudentRepository studentRepository;

@Test
void findByName_ReturnsTheStudent() {

//todo...
}
}


Complete example

Next we will create a spring Data JDBC application, create Repository layer which contains three methods and finally we will write test against H2 in-memory database.

4. Creating spring boot application

First, open the Spring initializr https://start.spring.io

Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact datajdbctest-example. Here I selected the Maven project - language Java 17 - Spring Boot 3.1.5 , Spring Data JDBC, and H2 Database.

Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(datajdbctest-example) file and downloads the project. Then, Extract the Zip file. 

Then, import the project on your favourite IDE.

Final Project directory:


Complete 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.1.5</version>
<
relativePath/> <!-- lookup parent from repository -->
</parent>
<
groupId>com.knf.dev.demo</groupId>
<
artifactId>datajdbctest-example</artifactId>
<
version>0.0.1-SNAPSHOT</version>
<
name>datajdbctest-example</name>
<
description>Demo project for Spring Boot</description>
<
properties>
<
java.version>17</java.version>
</
properties>
<
dependencies>
<
dependency>
<
groupId>org.springframework.boot</groupId>
<
artifactId>spring-boot-starter-data-jdbc</artifactId>
</
dependency>

<
dependency>
<
groupId>com.h2database</groupId>
<
artifactId>h2</artifactId>
<
scope>test</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>
<
configuration>
<
image>
<
builder>paketobuildpacks/builder-jammy-base:latest</builder>
</
image>
</
configuration>
</
plugin>
</
plugins>
</
build>

</
project>

spring-boot-starter-test starter will provide following libraries:

  • JUnit 
  • Spring Test & Spring Boot Test 
  • AssertJ
  • Hamcrest 
  • Mockito 
  • JSONassert 
  • JsonPath 

Spring Boot doesn’t require any special configuration for h2 database to work.


Create schema.sql

To generate a database schema at application start, the file schema.sql with the corresponding SQL commands for schema creation must be stored in the resources folder.

CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(250) NOT NULL,
email VARCHAR(250) NOT NULL,
age INT
);


Create test-student-data.sql

INSERT INTO students (id,name, email, age) VALUES
(101,'Alpha', 'alpha@knf.com', 50),
(102,'Beta', 'beta@knf.com', 40),
(103,'Gama', 'gama@knf.com', 30),
(104,'Pekka', 'pekka@knf.com', 20);

Later, as the part of testing we will use this file for loading data.


Create Model - Student.java

package com.knf.dev.demo.model;

import org.springframework.data.annotation.Id;

public class Student {

@Id
private Long id;

private String name;

private String email;

private Integer age;

public Student() {
}

public Student(Long id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}

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 getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

Use @Id on the primary key.


Spring Data JDBC – Student Repository

It doesn't make sense to test inherited default methods like save(), findById(), deleteById(), or findAll() from CrudRepository. If we are doing like so, means we are testing the framework.

So, i created three methods findByName() , findByAgeGreaterThan() , and findByAgeLessThan().

package com.knf.dev.demo.repository;

import com.knf.dev.demo.model.Student;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface StudentRepository extends CrudRepository<Student,Long> {

@Query(value = "select * from students as u where u.name = :name")
Optional<Student> findByName(String name);

@Query(value = "select * from students as u where u.age > :age")
List<Student> findByAgeGreaterThan(@Param("age")Integer age);

@Query(value = "select * from students as u where u.age < :age")
List<Student> findByAgeLessThan(@Param("age")Integer age);
}

  • findByAgeGreaterThan(): If we want to retrieve students whose age is greater than the given age.
  • findByName(): This method will get student entity by name.
  • findByAgeLessThan(): If we want to retrieve students whose age is less than the given age.
  • CrudRepository is a Spring Data interface for generic CRUD operations on a repository of a specific type.


DataJdbcTestExampleApplication.java

package com.knf.dev.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DataJdbcTestExampleApplication {

public static void main(String[] args) {
SpringApplication.run(DataJdbcTestExampleApplication.class, args);
}

}


Write unit test against H2 in-memory database

When using JUnit 4, this annotation should be used in combination with @RunWith(SpringRunner.class). But for this example  we are using JUnit 5, there’s no need to add the equivalent @ExtendWith(SpringExtension.class).

package com.knf.dev.demo;

import com.knf.dev.demo.model.Student;
import com.knf.dev.demo.repository.StudentRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.test.context.jdbc.Sql;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

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

@DataJdbcTest
public class StudentRepositoryTests {

@Autowired
private StudentRepository studentRepository;

@Test
@Sql({"/test-student-data.sql"})
void findByName_ReturnsTheStudent() {

Student student = studentRepository.findByName("Alpha").get();
assertThat(student).isNotNull();
assertThat(student.getEmail()).isEqualTo("alpha@knf.com");
assertThat(student.getName()).isEqualTo("Alpha");
assertThat(student.getId()).isEqualTo(101);
assertThat(student.getAge()).isEqualTo(50);
}

@Test
@Sql({"/test-student-data.sql"})
void findByAgeGreaterThan_ReturnsTheListStudents() {

List<Student> students = studentRepository.findByAgeGreaterThan(29);

//Convert list of students to list of id(Integer)
List<Integer> ids = students.stream()
.map(o -> o.getId().intValue())
.collect(Collectors.toList());

assertThat(students.size()).isEqualTo(3);
assertThat(ids).hasSameElementsAs(Arrays.asList(103,102,101));
}

@Test
@Sql({"/test-student-data.sql"})
void findByAgeLessThan_ReturnsTheListStudents() {

List<Student> students = studentRepository.findByAgeLessThan(31);

//Convert list of students to list of id(Integer)
List<Integer> ids = students.stream()
.map(o -> o.getId().intValue())
.collect(Collectors.toList());

assertThat(students.size()).isEqualTo(2);
assertThat(ids).hasSameElementsAs(Arrays.asList(104,103));
}
}

  • The @Sql annotation executes SQL scripts and SQL statements using datasource for testing.
  • assertThat is used to check the specified value matches the expected value. It will accept the two parameters, the first contains the actual value, and the second will have the object matching the condition.


5. Run the test

Or you can run the test using following command: 

mvn  test -Dtest=StudentRepositoryTests

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete