2024年12月31日 星期二

Java Database Unit Test - Spring + Database Rider

紀錄一下使用 Spring 搭配 Database Rider 的注意事項。

下面直接看範例,相關的說明也寫在注解上了。

最主要要注意的地方是,
如果不使用 Dabase Rider 的 Annotation 的話,跟之前這篇
使用 Dabase Rider 進行 Database 測試
的寫法是差不多的,只差在取得 Connection 的地方可以直接使用 Spring 幫我們裝配好的

JdbcTemplate 來取得 Datasource,再使用
DataSourceUtils.getConnection(dataSource) 取得 Connection。

如果想使用 Database Rider 的 Annotation 的話,需要使用
@DBRider(dataSourceBeanName = "test_DataSource")
這個 Annotaton 來設定 dataSourceBeanName,
@DBRider 本身就是 @ExtendWith(DBUnitExtension.class) 和 @Test 的組合,
但多提供了我們設定 dataSourceBeanName 的地方,
我們只要把設定在 Spring 中需要的 dataSourceBeanName 設定給 @DBRider,
之後 Database Rider 的 @DataSet, @ExpectedDataSet 這些 Annotation 就會直接用dataSourceBeanName 去找出 Spring 裝配好的 dataSource 進行 Database 連線,
所以不用再像這篇
使用 Dabase Rider 進行 Database 測試
一樣特別去設定 ConnectionHolder。

package test.dao;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.dbunit.DatabaseUnitException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import com.github.database.rider.core.api.configuration.DBUnit;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.DataSetFormat;
import com.github.database.rider.core.api.dataset.ExpectedDataSet;
import com.github.database.rider.core.api.exporter.DataSetExportConfig;
import com.github.database.rider.core.configuration.DBUnitConfig;
import com.github.database.rider.core.configuration.DataSetConfig;
import com.github.database.rider.core.configuration.ExpectedDataSetConfig;
import com.github.database.rider.core.dsl.RiderDSL;
import com.github.database.rider.core.exporter.DataSetExporter;
import com.github.database.rider.junit5.api.DBRider;

//讀取配置檔讓 Spring 處理 Bean 的依賴裝配
@SpringJUnitWebConfig(locations = {"file:WebContent/WEB-INF/mvc-config.xml"})
//如果要使用 Database Rider 的 Annotation (@Dataset, @ExpectedDataSet 等)
//才需要使用 @DBRider,
//@DBRider 內包含 @ExtendWith(DBUnitExtension.class) 和 @Test,
//並且多了一個 dataSourceBeanName 屬性,用來指定要使用的 Spring DataSourceBean 名稱
@DBRider(dataSourceBeanName = "test_DataSource")
@DBUnit(cacheConnection = false,
		allowEmptyFields = true)
@TestInstance(Lifecycle.PER_CLASS)
class DBRiderTest {
	
	//如果不使用 Database Rider 的 Annotation,
	//可以自行依需要取得 JdbcTemplate,進而取得 Datasource 和 Jdbc Connection
	@Autowired
	@Qualifier("test_JdbcTemplate")
	JdbcTemplate test_JdbcTemplate;
	
	final String testResourceFolderPath = "";
	final String backupDatasetResourcePath = "/backupDataset.xlsx";
	final String testDatasetResourcePath = "/testDataset.xlsx";
	
	final String[] includeTableList = new String[] {"test_table"};
	
	//因為 @ExportDataSet 似乎無法使用在 @BeforeAll 及 @AfterAll 上,
	//所以這裡不使用 Annotation,直接使用 API 進行 backup dataset 的 export
	@BeforeAll
	void exportBackupDataset() throws SQLException, DatabaseUnitException, URISyntaxException {
		URL backupDatasetResourceFolderUrl = DBRiderTest.class.getClassLoader().getResource(testResourceFolderPath);
		File backupDatasetResourceFolder = Paths.get(backupDatasetResourceFolderUrl.toURI()).toFile();
		String backupDatasetResourceFilePath = backupDatasetResourceFolder.getAbsolutePath() + backupDatasetResourcePath.replace("/", File.separator);
		
		DataSource dataSource = test_JdbcTemplate.getDataSource();
		Connection conn = DataSourceUtils.getConnection(dataSource);
		DataSetExporter.getInstance().export(conn,
											 new DataSetExportConfig()
											 .dataSetFormat(DataSetFormat.XLSX)
											 .includeTables(includeTableList)
											 .queryList(new String[] {})
											 .outputFileName(backupDatasetResourceFilePath));
		
		DataSourceUtils.releaseConnection(conn, dataSource);
	}
	
	//因為 @DataSet 似乎無法使用在 @BeforeAll 及 @AfterAll 上,
	//所以這裡不使用 Annotation,直接使用 API 進行 backup dataset 的 import
	@AfterAll
	void importBackupDataset() {
		//直接利用 Spring Inject 裝配好的 JdbcTemplate,並取得 DataSource 和 Connection
		DataSource dataSource = test_JdbcTemplate.getDataSource();
		Connection conn = DataSourceUtils.getConnection(dataSource);
		RiderDSL.withConnection(conn)
				.withDataSetConfig(new DataSetConfig(testResourceFolderPath + backupDatasetResourcePath)
						           //依需求設定 import dataset 要執行的動作,
						           //例如: Sql Server (MS Sql) 要 Insert/Update 主鍵時需要 SET IDENTITY_INSERT test_table ON
						           .executeStatementsBefore("")) 
				.withDBUnitConfig(new DBUnitConfig()
						          .addDBUnitProperty("allowEmptyFields", true))
				.createDataSet();
	
		DataSourceUtils.releaseConnection(conn, dataSource);
		//do other things
	}
	
	//使用 @DataSet 來 import test dataset
	@BeforeEach
	@DataSet(value = "testDataset.xlsx",
			 //依需求設定 import dataset 要執行的動作,
	         //例如: Sql Server (MS Sql) 要 Insert/Update 主鍵時需要 SET IDENTITY_INSERT test_table ON
	         executeStatementsBefore = {""})
	void importTestDataset() {
	}
	
	@Test
	void test() {
		System.out.println("test");
		Assertions.assertTrue(true);
	}
	
	//使用 @ExpectedDataSet Annotation 比較 database table 的資料是否和 expected dataset 一致
	@Test
	@ExpectedDataSet(value = "expectedDataset.xlsx")
	void testExpectedDatasetByAnnotation() throws DatabaseUnitException, SQLException {
		//做你想做的 Test
		//例如以下是比較 Actual dataset 和 expected dataset 的範例 (請再自己準備一個 expected dataset file):
		//自己修改一下 database table 的資料
		test_JdbcTemplate.update("UPDATE test_table SET title = 'ABC' WHERE id = 2");
		
		//因為使用了 @ExpectedDataSet Annotation,所以這裡就不需要再多寫用 RiderDSL 等的程式碼了
	}
	
	//也可以不使用用 @ExpectedDataSet Annotation,直接使用 RiderDSL 來比較 database table 的資料是否和 expected dataset 一致
	@Test 
	void testExpectedDataset() throws DatabaseUnitException, SQLException {
		//做你想做的 Test		
		//例如以下是比較 Actual dataset 和 expected dataset 的範例 (請再自己準備一個 expected dataset file):
		//自己修改一下 database table 的資料
		test_JdbcTemplate.update("UPDATE test_table SET title = 'ABC' WHERE id = 2");
		
		//比較 database table 的資料是否和 expected dataset 一樣
		DataSource dataSource = test_JdbcTemplate.getDataSource();
		Connection conn = DataSourceUtils.getConnection(dataSource);
		RiderDSL.withConnection(conn)
			    .withDataSetConfig(new DataSetConfig("expectedDataset.xlsx"))
			    .withDBUnitConfig(new DBUnitConfig()
					              .addDBUnitProperty("escapePattern", "\"?\"")
					              .addDBUnitProperty("caseSensitiveTableNames", true)
					              .addDBUnitProperty("allowEmptyFields", true))
		        .expectDataSet(new ExpectedDataSetConfig());
		
		DataSourceUtils.releaseConnection(conn, dataSource);
	}
}

沒有留言 :

張貼留言