JUnit 5の@TestInstance(Lifecycle.PER_CLASS)を指定したクラスでのモックオブジェクトの生成とスタブメソッドの設定は別の場所で行う

JUnit 5 × Kotlinで@BeforeAll@AfterAllを使いたかったり、Parameterized Testで@MethodSource用のstatic相当なメソッドを宣言したかったりするときなどは、対象のクラスに@TestInstance(Lifecycle.PER_CLASS)を指定します。
このアノテーションを指定したクラスでのモックオブジェクトの初期化処理でハマったので、その内容を備忘録として残しておきます。

公式ドキュメントに全て書いてありますが、@TestInstance(Lifecycle.PER_CLASS)を指定したクラスは、テスト時にインスタンスの生成が1回しか行われないので、スタブメソッドの設定を別のメソッド内で行う必要がありました

  • Lifecycle.PER_CLASS
    • When using this mode, a new test instance will be created once per test class.
  • Lifecycle.PER_METHOD (デフォルト)
    • When using this mode, a new test instance will be created for each test method, test factory method, or test template method.


例えば、MockKを使ってContextオブジェクトをモックしたいときは、以下のように書きます。

@TestInstance(Lifecycle.PER_CLASS)
class SampleTest {

  // モックオブジェクトの生成だけ最初に行う
  private val mockContext: Context = mockk()

  @BeforeAll
  fun beforeAll() {
    mockkStatic(/** 必要に応じて設定 **/)
    mockkObject(/** 必要に応じて設定 **/)
  }

  @BeforeEach
  fun beforeEach() {
    // 共通で使うスタブの振る舞いをセットする
    mockContext.run {
      every { getString(any(), *anyVararg()) } returns "sample"
    }
  }

  @AfterEach
  fun afterEach() {
    clearAllMocks()
  }

  @AfterAll
  fun afterAll() {
    unmockkAll()
  }

  @Test
  fun hogeTest() {
    // mockContextを利用した処理を行う
  }

  @Test
  fun fugaTest() {
    // mockContextを利用した処理を行う
  }

}


これを以下のように書いてしまったため、2回目にmockContextを使うときに期待通りの結果が返ってこなくなっていました。

@TestInstance(Lifecycle.PER_CLASS)
class SampleTest {

  // モックオブジェクトの生成と共通で使うスタブの振る舞いをセットしていた
  private val mockContext: Context = mockk {
    every { getString(any(), *anyVararg()) } returns "sample"
  }

  @BeforeAll
  fun beforeAll() {
    mockkStatic(/** 必要に応じて設定 **/)
    mockkObject(/** 必要に応じて設定 **/)
  }

  @BeforeEach
  fun beforeEach() {
    // ここでスタブの振る舞いをセットしていなかった
  }

  @AfterEach
  fun afterEach() {
    // ここでmockContextの情報がリセットされてしまった
    clearAllMocks()
  }

  @AfterAll
  fun afterAll() {
    unmockkAll()
  }

  @Test
  fun hogeTest() {
    // mockContextを利用した処理を行う
  }

  @Test
  fun fugaTest() {
    // mockContextを利用した処理を行う
  }

}


私のケースでは、元々期待通りに動いていたテストクラスに@MethodSourceを使ったParameterized Testを追加するために@TestInstance(Lifecycle.PER_CLASS)を指定したときに、この事象に遭遇しました。
今まで機械的@TestInstance(Lifecycle.PER_CLASS)を使っていたので、理解を深めるいい機会となりました。

参考資料