DEV Community

kaede
kaede

Posted on • Edited on

Kotlin Springboot -- Part 16 usecase の単体テスト (UT) を作る。port 以降は mockk を使う。

実装と方針


実装コード

@Component
class PersonsUsecase(
  private val personPort: PersonPort,
) {
  fun getAllPersons():Persons {
    return personPort.getAllPersons()
  }
Enter fullscreen mode Exit fullscreen mode

テストをする対象のコードはこれ
At Component で外部から使えるようにして
同じく At Component で外部から使えるようになっている
Port とその先の Gateway の getAllPersons を呼んで
Persons ドメインで受け取って、そのまま Rest に返す。


方針

TDD をやりたい。だから単体テストを先に書く。

実装のように Gateway のインスタンスをそのまま呼び出して使うと
Usecase の単体テストなのに、Gateway も同時にテストすることになってしまう。

それは Clean Archtechture な TDD ではない。
Rest のテストが通って、Usecase のテストが通って、そのあとに Gateway を実装したい。

なので Gateway を呼び出すための Port を mockk を用いてモックにする。



単体テスト


実際にやること

Port の偽物の中身を読んだ時にできる想定物と
Usecase の中身を読んだ時に成果物
これらが同じか比較する。


単体テストコード全体

import io.mockk.*
import org.amshove.kluent.shouldBeEqualTo
import org.junit.jupiter.api.Test

class PersonsUsecaseTest {
    @Test
    fun `全てのPersonを取得する`() {
        val persons = mockk<Persons>()
        val personPort = mockk<PersonPort>()
        val target = PersonsUsecase(personPort)
        every { personPort.getAllPersons() } returns persons
        val actual = target.getAllPersons()
        actual shouldBeEqualTo persons
    }
}
Enter fullscreen mode Exit fullscreen mode

関数はじめと返り値のモック

    @Test
    fun `全てのPersonを取得する`() {
        val persons = mockk<Persons>()
Enter fullscreen mode Exit fullscreen mode

At Test でテストコードだということを明記。
mockk で Persons 型の偽物インスタンスを作成。これは単純。


Port のモックの中身を呼び出す

同じく mockk で PersonPort 型のインスタンスを作成する。
しかし、これは中身のメソッドを呼び出されることになる。

val personPort = mockk<PersonPort>()
target.getAllPersons()
Enter fullscreen mode Exit fullscreen mode

たとえばこのまま mockk の偽物の中身を実装と同じ用に呼び出すと

no answer found for: PersonPort(#2).getAllPersons()
io.mockk.MockKException: no answer found for:
PersonPort(#2).getAllPersons()
Enter fullscreen mode Exit fullscreen mode

PersonPort は mockk で作られた偽物である。
なので中身が呼び出せない。MockKException が起きる


Port のモックの中身を呼び出した時に何が返ってくるか決める

every { personPort.getAllPersons() } returns persons
Enter fullscreen mode Exit fullscreen mode

ここで every { mockPort.functionName() } returns expectedMockName を使う。

すると {} 内部で Port のモックの中身の関数が呼ばれた時に
returns の後ろに書いた期待されるモックを返すようになる。


usecase として本体のインスタンスを呼び出す

val target = PersonsUsecase(personPort)
Enter fullscreen mode Exit fullscreen mode

mock の port を usecase のクラスに渡してインスタンス化する。

Usecase 自体をモックにするのは次回。


まとめ

Usecase の UT を書くには

@Test
fun `シナリオ名`() {
Enter fullscreen mode Exit fullscreen mode

と書き始めて

返り値の型の Domain のモックを用意。
内部で使われる Port のモックを用意。

every {} return で Port のモックの使用される関数が呼ばれた場合
Domain のモックが返されるように設定する

Port のモックを使って Usecase のインスタンスを作る

usecase のインスタンスの使用される関数が呼ばれた場合
Port のモックの中身が呼ばれた場合と同一結果になるか比較する。

以上。


次回

Usecase も本物に限りなく近いモックを用意してテストするようにする。

Top comments (0)