元営業WEBエンジニアのアプリ開発日記

営業出身のWEB系エンジニアが気になったものから作ってはメモを残してくブログ

SpringBootでDBも絡めた単体テストを書いてみる

概要

Spring絡みのことを色々書いたので、SpringBootでの単体テストについてもちょっとだけ書いとこう

SpringBootでの単体テスト

メソッド単位でちゃんと書く的なことじゃなくて大元のメソッドからDBまでぐるっと動作させて期待値通りのものが出てるか確認してみます。

書いてみたテスト

とりあえず書いた最小限のテスト構成を書いておこう。

テストクラス

  • @RunWith(SpringRunner.class)
    • これ書くと@AutowiredとかSpringの機能が使えるようになるらしい
  • @SpringBootTest
    • application.propertiesなどを読み込めるようにするらしい
  • @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class })
    • Listenerを設定この辺設定しとくと色々できるようになるらしい
  • @DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
    • データベースをCSVで読み込めるように設定
  • @DatabaseSetup(value = "/xyz/ucwork/future/mutation/")
    • テスト実行前のデータベースの状態を定義できる
  • @ExpectedDatabase(value = "/xyz/ucwork/future/mutation/expect/", table = "members", assertionMode = DatabaseAssertionMode.NON_STRICT)
    • テスト実行後のデータベースの期待値を定義できる

/future/src/test/java/xyz/ucwork/future/resolvers/MutationTest.java

package xyz.ucwork.future.resolvers;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;

import xyz.ucwork.future.CsvDataSetLoader;

@RunWith(SpringRunner.class)
@SpringBootTest
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
public class MutationTest {

    @Autowired
    private Mutation mutation;

    @Test
    @DatabaseSetup(value = "/xyz/ucwork/future/mutation/")
    @ExpectedDatabase(value = "/xyz/ucwork/future/mutation/expect/", table = "members", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void testRegistMember() {
        mutation.registMember("testName");
    }

}

データベースの状態をCSVで設定できるようにするためのクラス

お作法のようにそのまんま書いて、テストクラスの@DbUnitConfigurationアノテーションで指定

/future/src/test/java/xyz/ucwork/future/CsvDataSetLoader.java

package xyz.ucwork.future;

import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.csv.CsvURLDataSet;
import org.springframework.core.io.Resource;

import com.github.springtestdbunit.dataset.AbstractDataSetLoader;

public class CsvDataSetLoader extends AbstractDataSetLoader {
    @Override
    protected IDataSet createDataSet(Resource resource) throws Exception {
        return new CsvURLDataSet(resource.getURL());
    }
}

テスト実行前のデータベース状態設定

カラム名と設定したい値を書く。
/future/src/test/resources/xyz/ucwork/future/mutation/members.csv

"id","name","created_at","updated_at"

table-ordering.txtにテーブル名を改行入れながら書くと上から順番にテーブルに書き込んでくれる
/future/src/test/resources/xyz/ucwork/future/mutation/table-ordering.txt

members

テスト実行後のデータベース状態設定

期待値確認したいカラムと値だけ書いとく。auto_incrementのidや都度変わる日時は期待値に含めてない
/future/src/test/resources/xyz/ucwork/future/mutation/expect/members.csv

"name"
"testName"

/future/src/test/resources/xyz/ucwork/future/mutation/expect/table-ordering.txt

members

プロパティーを設定する

単体テストを実施するデータベースを作成して設定する
/future/src/test/resources/application.yml

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/future_unit_test
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver

余談

ちょっくらgraphql-spring-boot-starterバージョンを5.0.2に上げてみたらこんなエラーが出てきた。
4.4.0に戻したら治った。優秀なエンジニアの人たちがモミにもんでよろしく動くようにしてくれてると祈って見なかったことにしおく

Caused by:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

Caused by:
java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

まとめ

本当はメール送信をスタブ化したときにすごく苦労したのでそのへん書こうかとおもいましたが、もう気力がなくなってきたのでGithubのテストの場所を貼り付けておきます。

「AmazonSESのモック化」と「Mockito2.x系でWhiteBoxがDepricatedされた問題」が大きくつまずいたところな気がする