postgresqlを使用したdbUnitテストを行った時に、DBUnitのデフォルトのDataFactoryであるPostgresqlDataTypeFactoryを使用すると思うのですが、postgresqlのテーブルの中に特殊データ型に該当するカラムがあると上手く持ってこれない事象が発生しました。
特殊データ型というのは一般的なDBにあるような型(int,timestamp,varcharとか)ではなく、postgresql特有で定義されている型の事です。(_text、tsvectorとか)
https://www.postgresql.jp/document/9.4/html/datatype.html
事象
Spring batchのRepositoryTestを特殊データ型のカラムが含まれているテーブルに対してdbUnitを用いてSetupしようとしたときにエラーが発生。
対象のテーブル
postgresql公式のチュートリアルにあるテーブル群を使っています。
PostgreSQL Sample Database
今回はfilmテーブルからのSELECTをテストします。
filmテーブル

この中でpostgresqlの特殊データ型に属するのは
- mpaa_rating
- _text
- tsvector
になります。それ以外は通常のやり方でJavaとのマッピングが可能です。
http://beanql.osdn.jp/type_map.html
SQL
Mybatisの定義です。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" < mapper namespace = "itouoti.spring.batch.repository.jobsRepository.job1.Job1Repository" > < select id = "sarchfilmData" resultType = "itouoti.spring.batch.repository.commonDTO.FilmDTO" parameterType = "map" > <![CDATA[ SELECT film_id AS filmId, title, description, release_year AS releaseYear, language_id AS languageId, rental_duration AS rentalDuration, rental_rate AS rentalRate, length, replacement_cost AS replacementCost, rating, last_update AS lastUpdate, special_features AS specialFeatures, fulltext FROM film WHERE film_id = #{filmId} ]]> </ select > </ mapper > |
テストコード
DBUnitを使ってエクセルファイルからテーブルデータをセットアップします。
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | package itouoti.spring.batch.repository.jobsRepository.job1; import *; /** * Theoriesテスト */ @RunWith (Theories. class ) @ContextConfiguration (locations = { "classpath:META-INF/jobs/job1/job1Test.xml" }) @TestExecutionListeners ({ DependencyInjectionTestExecutionListener. class , DirtiesContextTestExecutionListener. class , TransactionalTestExecutionListener. class , DbUnitTestExecutionListener. class }) @Transactional @DbUnitConfiguration (dataSetLoader = XlsDataSetLoader. class ) @Slf4j /** * job1Repository sarchfilmData Test * @author itouoti */ public class Job1RepositorySerchFilmDataTest { @ClassRule public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); @Inject Job1Repository target; @Inject ResourceLoader resourceLoader; @Inject JdbcTemplate jdbcTemplate; @Inject TransactionAwareDataSourceProxy masterDataSource; @Value ( "${xlsx.expectedDataFile}" ) String xlsxExpectedDataFile; // エクセルファイルパス private static String xlsxPath = "classpath:itouoti/spring/batch/repository/jobsRepository/job1/" ; // テストパターン用エクセルオブジェクト private static IDataSet xlsDataSet; // assertエラー格納用List private static List<AssertionError> assertionErrors = new ArrayList<AssertionError>(); private static String jdbcProps = "classpath:batch-application.properties" ; private static Properties props; /** * テストメソッド起動前設定 * @throws Exception 例外 */ @BeforeClass public static void setUpBeforeTest() throws Exception { log.info( "□sarchfilmData test is start" ); // xlsxファイルの読み込み File file = ResourceUtils.getFile(xlsxPath + "testList.xlsx" ); xlsDataSet = new XlsDataSet(file); // propertiesファイルの読み込み props = LoadProperties.load(jdbcProps); // 外部キー制約の解除 String disableAlter = "ALTER TABLE film DISABLE TRIGGER ALL" ; ExecSQL.execSQLExecSQL(props, disableAlter, false ); } /** * テスト毎起動前設定 * @throws Exception 例外 */ @Before public void setUp() throws Exception { } /** * テスト毎検証後処理 */ @After public void cleanUp() { // テスト後の後処理があれば記述 } /** * テストメソッド実行後処理 * @throws Exception 例外 */ @AfterClass public static void creanUpAfterTest() throws Exception { // テスト後の後処理があれば記述 if (assertionErrors.size() > 0 ) { for (AssertionError e : assertionErrors) { log.error(e.getMessage()); e.printStackTrace(); } log.error(assertionErrors.size() + "件のAssertionErrorが発生しています" ); fail(assertionErrors.size() + "件のAssertionErrorが発生しています" ); } log.info( "□sarchfilmData test is end" ); // 外部キー制約の設定 String disableAlter = "ALTER TABLE film ENABLE TRIGGER ALL" ; ExecSQL.execSQLExecSQL(props, disableAlter, false ); } /** * job1Repository.sarchfilmDataテスト * @throws Exception DBUnitのxlsx読み込み例外 */ @Theory @DatabaseSetup ( "classpath:itouoti/spring/batch/repository/jobsRepository/job1/setUp_data.xlsx" ) public void sarchfilmDataTest( Job1RepositorySerchFilmDataParam testParam) throws Exception { log.info( "■ TestNo." + testParam.no + " sarchfilmData test is start" ); try { // STEP1 メソッドのテスト Map<String, Object> map = new HashMap<>(); map.put( "filmId" , testParam.getFilmId()); FilmDTO result = target.sarchfilmData(map); // STEP2 取得件数の検証 assertThat(result.getTitle(), is(testParam.getExpectTitle())); assertThat(result.getDescription(), is(testParam.getExpectDescription())); // STEP3 テーブル状態の検証 ITable actual1 = DBUnitSetUp.getAtualDataTable(masterDataSource, "public.film" ); ITable expected1 = DBUnitSetUp.getExpectedDataTable(resourceLoader, xlsxPath + xlsxExpectedDataFile, "film" ); org.dbunit.Assertion.assertEquals( new SortedTable( new TrimTable(expected1)), new SortedTable( new TrimTable(actual1), expected1 .getTableMetaData())); } catch (AssertionError e) { // AssertionErrorsに貯める log.error(testParam.no + "Memo = " + testParam.getMemo()); assertionErrors.add(e); } catch (Exception e) { // AssertionErrorsに貯める log.error(e.getMessage()); assertionErrors.add( new AssertionError(e)); } finally { log.info( "■ TestNo." + testParam.no + " sarchfilmData test is end" ); } } /** * job1Repository.sarchfilmDataテスト用パラメータ生成 * @return sarchfilmData[] * @throws Exception 例外 */ @DataPoints public static Job1RepositorySerchFilmDataParam[] inputTestParams() throws Exception { // テスト用シート名 String testParamSheet = "param" ; ArrayList<Job1RepositorySerchFilmDataParam> paramList = new ArrayList<Job1RepositorySerchFilmDataParam>(); try { ITable paramTable = xlsDataSet.getTable(testParamSheet); for ( int i = 0 ; i < paramTable.getRowCount(); i++) { Job1RepositorySerchFilmDataParam param = new Job1RepositorySerchFilmDataParam(); // 試験No param.setNo(TestHelper.toStr(paramTable, i, "no" )); // filmId param.setFilmId(TestHelper.toInt(paramTable, i, "filmId" )); // 期待値(件数) param.setExpectCount(TestHelper.toInt(paramTable, i, "expectCount" )); // 期待値(title) param.setExpectTitle(TestHelper.toStr(paramTable, i, "expectTitle" )); // 期待値(description) param.setExpectDescription(TestHelper.toStr(paramTable, i, "expectDescription" )); // 備考 param.setMemo(TestHelper.toStr(paramTable, i, "memo" )); paramList.add(param); } } catch (Exception e) { e.printStackTrace(); } return paramList.toArray( new Job1RepositorySerchFilmDataParam[paramList.size()]); } } |
エラー内容
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | [2019/06/16 13:47:33] [main] [o.d.util.SQLHelper ] [WARN ] film.special_features data type (2003, '_text') not recognized and will be ignored. See FAQ for more information. [2019/06/16 13:47:33] [main] [o.d.util.SQLHelper ] [WARN ] film.fulltext data type (1111, 'tsvector') not recognized and will be ignored. See FAQ for more information. [2019/06/16 13:47:33] [main] [o.s.t.c.TestContextManager] [WARN ] Caught exception while allowing TestExecutionListener [com.github.springtestdbunit.DbUnitTestExecutionListener@7ba18f1b] to process 'before' execution of test method [public void itouoti.spring.batch.repository.jobsRepository.job1.Job1RepositorySerchFilmDataTest.sarchfilmDataTest(itouoti.spring.batch.repository.jobsRepository.job1.Job1RepositorySerchFilmDataParam) throws java.lang.Exception] for test instance [itouoti.spring.batch.repository.jobsRepository.job1.Job1RepositorySerchFilmDataTest@581d969c] org.dbunit.dataset.NoSuchColumnException: film.SPECIAL_FEATURES - (Non-uppercase input column: special_features) in ColumnNameToIndexes cache map. Note that the map's column names are NOT case sensitive. at org.dbunit.dataset.AbstractTableMetaData.getColumnIndex(AbstractTableMetaData.java:117) at org.dbunit.operation.AbstractOperation.getOperationMetaData(AbstractOperation.java:89) at org.dbunit.operation.AbstractBatchOperation.execute(AbstractBatchOperation.java:150) at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79) at com.github.springtestdbunit.DbUnitRunner.setupOrTeardown(DbUnitRunner.java:183) at com.github.springtestdbunit.DbUnitRunner.beforeTestMethod(DbUnitRunner.java:75) at com.github.springtestdbunit.DbUnitTestExecutionListener.beforeTestMethod(DbUnitTestExecutionListener.java:185) at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:269) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.RunPrepareTestInstanceCallbacks.evaluate(RunPrepareTestInstanceCallbacks.java:64) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.springframework.test.context.junit4.statements.SpringFailOnTimeout.evaluate(SpringFailOnTimeout.java:87) at org.springframework.test.context.junit4.statements.ProfileValueChecker.evaluate(ProfileValueChecker.java:101) at org.junit.experimental.theories.Theories$TheoryAnchor$1$1.evaluate(Theories.java:232) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithCompleteAssignment(Theories.java:218) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithAssignment(Theories.java:204) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithIncompleteAssignment(Theories.java:212) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithAssignment(Theories.java:202) at org.junit.experimental.theories.Theories$TheoryAnchor.evaluate(Theories.java:187) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.springframework.test.context.junit4.statements.ProfileValueChecker.evaluate(ProfileValueChecker.java:101) at org.springframework.test.context.junit4.rules.SpringClassRule$TestContextManagerCacheEvictor.evaluate(SpringClassRule.java:242) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206) |
special_featuresカラムが原因で失敗しています。
また、warnで認識できない型もいくつか警告されています。
原因
dbUnitの標準のpostgresql用のデータファクトリーである「org.dbunit.ext.postgresql.PostgresqlDataTypeFactory」が対応していない型がテーブルに存在するため。
解決策
PostgresqlDataTypeFactoryをextendsして、カスタムファクトリーを作成する。
test-context.xml
まず、 PostgresqlDataTypeFactory を設定しているところを修正します。
1 2 3 4 5 6 7 8 9 | < bean id = "dbUnitDatabaseConfig" class = "com.github.springtestdbunit.bean.DatabaseConfigBean" > < property name = "datatypeFactory" > < bean class = "itouoti.spring.batch.testCommon.PsqlArrayDataTypeFactory" /> <!-- <bean class="org.dbunit.ext.postgresql.PostgresqlDataTypeFactory" /> --> </ property > </ bean > |
PsqlArrayDataTypeFactory.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class PsqlArrayDataTypeFactory extends PostgresqlDataTypeFactory { public DataType createDataType( int sqlType, String sqlTypeName, String tableName, String columnName) throws DataTypeException { if (sqlType == 2003 ) { if (sqlTypeName.equals( "_text" )) { return new ArrayDataType(sqlTypeName, sqlType, false ); } } if (sqlType == 1111 ) { if (sqlTypeName.equals( "tsvector" )) { return new TsVectorDataType(sqlTypeName, sqlType); } } if (sqlType == 12 ) { if (sqlTypeName.equals( "mpaa_rating" )) { return new Mpaa_ratingDataType(sqlTypeName, sqlType); } } return super .createDataType(sqlType, sqlTypeName); } } |
警告が出ていたSQLTypeとSQLNameの組み合わせが来た時に専用のクラスに飛ばすようにします。
ArrayDataType.java
参考のStackOverFlowを見てください。
TsVectorDataType.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package itouoti.spring.batch.testCommon.dataType; import *; public class TsVectorDataType extends AbstractDataType { public TsVectorDataType(String name, int sqlType) { super ( "tsvector" , Types.OTHER, String. class , false ); } public Object typeCast(Object value) throws TypeCastException { if (value == null ) { return null ; } PGobject obj = new PGobject(); obj.setType( "tsvector" ); try { obj.setValue(value.toString()); } catch (SQLException e) { e.printStackTrace(); } return obj; } public Object getSqlValue( int column, ResultSet resultSet) throws SQLException, TypeCastException { return resultSet.getString(column); } } |
文字列を無理やりセットしています。
Mpaa_ratingDataTypeについてもやり方は同じなので詳しくはgithubソースを参照してください。
ちなみにセットアップデータのエクセルはこんな感じです。

結果

成功しました。
ソース
https://github.com/itouoti12/spring-batch
コメントを書く