Spring boot
is the most popular framework for creating microservice API
. It offers great support for API testing
, and for persistence during testing, it is common to use an in-memory database
that is dropped once testing is complete.
One widely used in-memory database
for testing in Spring Boot
is the H2 database engine
. H2
is an open-source Java-based database
that supports SQL
and JDBC
. It can be run embedded
inside an application or as a stand-alone server
.
The advantage of using H2
as an in-memory database
is that it relies on system memory
or RAM
for storage, rather than disk space
. This allows for faster storing and retrieving of data compared to disk-based databases. However, once the application is terminated, all the data stored in H2
is lost.
H2 is an in-memory database, hence data will not persist on the disk, so it should used in development and testing only
Liquibase
can be used in conjunction with H2
for managing database changes and schema evolution during development and testing
In the below example, The Controler
will retrieve data from the Mysql Database
, but for testing purposes, H2
will be used.
package com.example.SpringLiquibase.controller;
import com.example.SpringLiquibase.entity.Apple;
import com.example.SpringLiquibase.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class LiquibaseController {
@Autowired
AppleRepository appleRepository;
@GetMapping
public List<Apple> getApple() {
return appleRepository.findAll();
}
}
package com.example.SpringLiquibase.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Apple {
@Id
@Column
@GeneratedValue
private Long appleId;
@Column
private String appleName;
}
package com.example.SpringLiquibase.repository;
import com.example.SpringLiquibase.entity.Apple;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
}
package com.example.SpringLiquibase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringLiquibaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringLiquibaseApplication.class, args);
}
}
\src\main\resources\db\changelog\changelog.mysql.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="wesome" id="1692446190573-1">
<createTable catalogName="appledb" tableName="apple">
<column autoIncrement="true" name="apple_id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="apple_name" type="VARCHAR(255)"/>
<column defaultValueComputed="CURRENT_TIMESTAMP" name="current_date_time" type="datetime"/>
</createTable>
<rollback>
<dropTable tableName="apple"/>
</rollback>
</changeSet>
<changeSet author="wesome" id="1692701200879-2">
<insert catalogName="appledb" tableName="apple">
<column name="apple_name" value="Fuji"/>
<column name="current_date_time" valueDate="now()"/>
</insert>
<rollback>
<delete tableName="apple">
<where>
apple_name = 'Fuji'
</where>
</delete>
</rollback>
</changeSet>
<changeSet author="wesome" id="1692701200879-3">
<insert catalogName="appledb" tableName="apple">
<column name="apple_name" value="Gala"/>
<column name="current_date_time" valueDate="now()"/>
</insert>
<rollback>
<delete tableName="apple">
<where>
apple_name = 'Gala'
</where>
</delete>
</rollback>
</changeSet>
</databaseChangeLog>
\src\main\resources\application.properties
server.servlet.context-path=/liquibase
spring.datasource.url=jdbc:mysql://localhost:3306/appledb?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=rootroot
spring.liquibase.change-log=db/changelog/changelog.mysql.xml
spring.jpa.show-sql=true
package com.example.SpringLiquibase.controller;
import com.example.SpringLiquibase.entity.Apple;
import com.example.SpringLiquibase.repository.AppleRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
@DataJpaTest
class LiquibaseControllerTest {
@Autowired
AppleRepository appleRepository;
@Test
void getApple() {
List<Apple> apples = appleRepository.findAll();
Assertions.assertFalse(apples.isEmpty());
assertAll(() -> Assertions.assertFalse(apples.isEmpty(), "apple list should not be empty"), () -> Assertions.assertTrue(apples.size() == 2, "apple list should contain 2 elements"), () -> assertEquals(1, apples.get(0).getAppleId(), "appleId should be 1"), () -> assertEquals("Macintosh", apples.get(0).getAppleName(), "appleName should be Macintosh"), () -> assertEquals(2, apples.get(1).getAppleId(), "appleId should be 2"), () -> assertEquals("Jonagold", apples.get(1).getAppleName(), "appleName should be Jonagold"));
}
}
\src\test\resources\db\changelog\changelog.mysql.test.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="wesome" id="1692446190573-1">
<createTable catalogName="appleDbTest" tableName="apple">
<column autoIncrement="true" name="apple_id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="apple_name" type="VARCHAR(255)"/>
<column defaultValueComputed="CURRENT_TIMESTAMP" name="current_date_time" type="datetime"/>
</createTable>
<rollback>
<dropTable tableName="apple"/>
</rollback>
</changeSet>
<changeSet author="wesome" id="1692701200879-2">
<insert catalogName="appleDbTest" tableName="apple">
<column name="apple_name" value="Macintosh"/>
<column name="current_date_time" valueDate="now()"/>
</insert>
<rollback>
<delete tableName="apple">
<where>
apple_name = 'Macintosh'
</where>
</delete>
</rollback>
</changeSet>
<changeSet author="wesome" id="1692701200879-3">
<insert catalogName="appleDbTest" tableName="apple">
<column name="apple_name" value="Jonagold"/>
<column name="current_date_time" valueDate="now()"/>
</insert>
<rollback>
<delete tableName="apple">
<where>
apple_name = 'Jonagold'
</where>
</delete>
</rollback>
</changeSet>
</databaseChangeLog>
\src\test\resources\application.properties
spring.test.database.replace=none
spring.jpa.hibernate.ddl-auto=none
spring.liquibase.change-log=db/changelog/changelog.mysql.test.xml
spring.liquibase.liquibase-schema=appleDbTest
spring.liquibase.drop-first=true
spring.datasource.url=jdbc:h2:mem:appleDbTest;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS appleDbTest
spring.datasource.username=root
spring.datasource.password=rootroot
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>SpringLiquibaseApplication</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringLiquibaseApplication</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-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Once the Application is Up and running, the inserted record can be verified by
SELECT * FROM appledb.apple;
The API result can be verified by executing
curl --location 'http://localhost:8081/liquibase/'