LoginSignup
49
3

Mockito-Kotlinを使ってテストコードを書いてみる

Last updated at Posted at 2023-12-06

RetailAI Adventurers Advent Calendar 2023の7日目の記事です。

Mockito-Kotlinの基本的な使い方をまとめてみます。

Javaなどでテストダブルを作成できるMockitoというライブラリがあります。
以前、KotlinとSpring Bootを使ったサンプルアプリのテストコードを書くためにMockitoを使ってみたことがあります。せっかくKotlin用のライブラリがあるので、当時のメモをもとにMockito-Kotlinで書き直してみました。

テスト対象のコード

@Service
class UserService {
    @Autowired
    lateinit var repository: UserRepository

    fun getUserById(id: Int): User? {
        return repository.findById(id).orElse(null)
    }
}

このクラスは以下のUserRepopsitoryクラスに依存しています。
JPAリポジトリを利用してデータベースにアクセスするコードです。
(自分でテスト用のクラスを作ろうとすると、オーバーライドしないといけないメソッドが多くて結構大変)

@Repository
interface UserRepository: JpaRepository<User, Int> {}

UserServiceだけをテストしたいので、UserRepositoryをモック化したい。

Mockit-Kotlinを使ってみる

build.gradle に以下の依存関係を追加。

testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.0'

テストクラスを作成

class UserServiceTestByKotlin {
    @Mock
    lateinit var mockUserRepository: UserRepository

    @InjectMocks
    lateinit var userService: UserService

    @BeforeEach
    fun init() {
        MockitoAnnotations.openMocks(this)
    }
}

@Mockでmock化するクラスを指定。
@InjectMocksでmock化するクラスに依存しているクラスを指定する。

モックを使ったテスト

@Test
fun shouldInvokeFindById() {
    userService.getUserById(10)
    userService.getUserById(20)
    userService.getUserById(20)

    verify(mockUserRepository).findById(10)    ///(1)
    verify(mockUserRepository, atLeast(1)).findById(10)

    verify(mockUserRepository, times(3)).findById(anyInt())
}

verify()でメソッドが呼び出された回数のテストを行う。
xx回、最大(最小)xx回などを第2引数で指定する。
///(1) は第2引数にtimes(1)を渡したのと同じ動作らしい。

スタブを使ったテスト

private val testUser = User(1, "taro", 33)

@Test
fun shouldReturnUserDetailFromRepository() {
    whenever(mockUserRepository.findById(1))
        .thenReturn(Optional.of(testUser))
        
    val user = userService.getUserById(1)
    assertThat(user!!.id).isEqualTo(1)
    assertThat(user.userName).isEqualTo("taro")
    assertThat(user.age).isEqualTo(33)
}

whenever()thenReturn()を使う。
戻り値を指定しない場合、null(プリミティブ型なら初期値)になっている。

@Test
fun stubDefaultValue() {
    val user = userService.getUserById(2)
    assertThat(user).isNull()
}

以下のようにも書ける。

mockUserRepository.stub {
    on { findById(1) } doReturn Optional.of(testUser)
 }

JavaでMockitoを使ったときの違いなど

whenwhenever

Stubで値を返す値を指定するとき、Mockitoではwhenを使う。
しかしKotlinではwhenが予約語になっているので、wheneverを使えるようになっている。
ちなみに、ソースコードを見るとwhenの糖衣構文らしい。

@Suppress("NOTHING_TO_INLINE")
inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
    return Mockito.`when`(methodCall)!!
}

any()

whenever(mock.example(any(User::class.java))).thenReturn(11)

とか

verify(mock).example(any(User::class.java))

などが、NullPointerExceptionになる。

any()などが使えるようになる!」て堂々と書いていたのに......?と思ったが、上のコードで使っていたany()はMockitoで定義されているものだった。

Spring Bootのプロジェクトを作ったときに追加していた依存関係

testImplementation 'org.springframework.boot:spring-boot-starter-test'

この中で読み込まれるパッケージにMockitoが含まれており、MockitoとMockito-Kotlinのany()が衝突していたらしい。
(だから、引数を渡さないとIDEAがエラーを表示していた)

whenever(mockUserRepository.example(org.mockito.kotlin.any())).thenReturn(11)
mockUserRepository.example(testUser)
verify(mockUserRepository).example(org.mockito.kotlin.any())

こんな感じで、パッケージ名まで明示してやると動くようになった。

参考

49
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
3