この記事の内容
自作のアプリでkotlin.random.Random を使った関数のテストを書きたくなったときに意外と苦労した備忘録。
Spring Bootを利用したwebアプリケーションで、テストコードにはJunit、Mockito-Kotlinなどを使用しています。
例えばこんなコード
@Service
class SomeService {
// この関数をテストする
fun someFunction(): Int {
// 1から100までの乱数を生成
val randomValue = Random.nextInt(1, 101)
// ゾロ目の時だけ10倍して返す
return if(randomValue % 11 == 0) {
randomValue * 10
} else {
randomValue
}
}
}
テストを書こう
この関数のテストで、以下の点を確かめます。
- 1から100までの乱数を生成していること(すなわち
Random.nextInt(1, 101)を呼び出していること) - 11の倍数のときには10倍の値を返すこと
Random のテストのためにシードを固定ような方法もありますが、mockを使えば解決しそう、ということで以下のようなコードを書きました。
class SomeServiceTest {
@Mock
private lateinit var random: Random
@InjectMocks
private lateinit var someService: SomeService
@Test
fun testSomeFunction() {
whenever(random.nextInt(1, 101)).thenReturn(11)
val actual = someService.someFunction()
verify(random).nextInt(1, 101) // ①
assertThat(actual).isEqualTo(110) // ②
}
// それ以外のテストケースは省略
}
これでテストケースをパスすると思いきや、①では、random.nextInt(1, 101) は呼び出されていないことになっており、②のactualの値はwhenever ~ thenReturnで設定したものと異なる値が入っていました。
何が問題か
Random.nextInt()がstaticな関数であったため、mockと置き換えられていなかったようです。
というわけで、Serviceの実装部分でDIを使うように書き直しました。
まず、Beanを登録。
@Configuration
class Config {
@Bean
fun random(): Random = Random.Default
}
そして、ServiceクラスではこのBeanを使う
@Service
class SomeService {
@Autowired
private lateinit var random: Random
fun someFunction(): Int {
// クラスのフィールドのrandomを使うように修正
val randomValue = random.nextInt(1, 101)
return if(randomValue % 11 == 0) {
randomValue * 10
} else {
randomValue
}
}
}
これで先ほど書いたテストコードをパスするようになりました。
まとめ
他にも mockStatic を利用する方法があるようです。今回はverify(呼び出し回数のテスト)がうまく動かなくてやめました。
テストのために実装を変える形ではあるので、やや不満があります。良い方法をご存知の方は教えていただけると喜びます。