why
前回 Usecase の UT まで DI を活用して実装したので
今回は Gateway の UT をやる。
Gateway のクラスから Test を空で作成
IntelliJ でクラス名にカーソルを当てて Alt Enter すると
テストの作成のウィンドウが開く。
メンバー込みでの生成を選ぶ。
すると次にモジュールをどれにするかと、
内部のテストディレクトリのどこに作るかがでてくる。
Gateway のディレクトリの test/kotlin を選ぶ。
するとこのように
gateway モジュールの src/main/ と並列の src/test/ の
内部にクラス名+Test の名前でテストファイルが生成される。
package com.kaede import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.* internal class PersonGatewayTest { @Test fun getAllPersons() { } }
ここまで中身が生成される。
Gateway の依存を記入する
Gateway の依存モジュールを
build.gradle.kts に記入して maven reload する
implementation(project(":port")) implementation(project(":driver")) implementation(project(":domain"))
- Gateway が実装する元のインターフェイスの Port
- 関数の中身で使う Driver
- Driver から返ってきた Entity を詰め替える為の Domain
Driver モジュールに Entity と Driver のクラスたちを作成
DB から返ってくる型として Entity の入れ物が必要。
Driver モジュールと密結合なので、Driver モジュール内部に作る。
Entity についてはここを参照。
String や Int などの Primary な値を入れる。
data class PersonEntity(val name: String , val age: Int)
PersonEntity の Entity を作って
class PersonDriver { fun findAll() : List<PersonEntity>{ TODO() } }
返り値が PersonEntity のリストになる PersonDriver のガワを作る。
GatewayTest のためのライブラリの依存を追加する
build.gradle.kts に
testImplementation("org.amshove.kluent:kluent:1.68") testImplementation("io.mockk:mockk:1.13.2")
Usecase と同じく、Gateway にも kluent と mockk を追加する
internal class PersonGatewayTest { @InjectMockKs lateinit var personGateway: PersonGateway @MockK lateinit var driver: PersonDriver
すると Gateway でも Mockk シリーズが使えるようになる。
Gateway と Driver を作っておく
@Test fun `全ての Person を取得する`() { MockKAnnotations.init(this) val persons = mockk<Persons>() val personsEntity = listOf(mockk<PersonEntity>()) every { driver.findAll() } returns personsEntity target.getAllPersons() shouldBeEqualTo personsEntity }
こうやって、
Usecase と同じようにテストを作ると正しくない。
なぜなら Usecase では
- Usecase の関数本体の返り値
- 内部で Port から呼ばれる Gateway の返り値
ともに Domain だったので比較ができたが
Gateway では
- Gateway の関数本体の返り値は Domain
- Driver の返り値は Entity
なので型で比較することができない。
@Test fun `全ての Person を取得する`() { MockKAnnotations.init(this) val personsEntity = listOf( PersonEntity("ドメイン太郎", 100), PersonEntity("ドメイン次郎", 80), ) every { driver.findAll() } returns personsEntity val persons = Persons(listOf( Person( Name("ドメイン太郎"), Age(100) ), Person( Name("ドメイン次郎"), Age(80) ), )) target.getAllPersons() shouldBeEqualTo persons }
なので、実際に Entity と Domain を同じ値で作って
Enitity と同じ値で作られた Domain ができるかをテストする。
Gateway の実装
TDD として、テスト後に実装をする
@Component class PersonGateway() : PersonPort { // Port の関数を実装するから override fun getAllPersons(): Persons { TODO() // return Persons(listOf(Person(Name("a"),Age(2)))) } }
現在のガワがこれ。
ここにテストと同じように Driver を Null で定義して
@Component class PersonGateway() : PersonPort { // Port の関数を実装するから @Autowired lateinit var personDriver: PersonDriver override fun getAllPersons(): Persons { val persons = personDriver.findAll() val personsDomain= persons.map { personEntity -> Person(Name(personEntity.name), Age(personEntity.age)) } return Persons(personsDomain) } }
Driver の結果の Entity を Map して Person の list にして
Persons に詰めて返す
これで Gateway も UT と実装を作成できた!
Expected :Persons(list=[Person(name=Name(value=ドメイン太郎), age=Age(value=100)), Person(name=Name(value=ドメインさぶ郎), age=Age(value=80))]) Actual :Persons(list=[Person(name=Name(value=ドメイン太郎), age=Age(value=100)), Person(name=Name(value=ドメイン次郎), age=Age(value=80))])
Expected の Domain の中身だけをさぶろうにするとちゃんと落ちる。
Top comments (0)