JUnit 5の@TestInstance(Lifecycle.PER_CLASS)を指定したクラスでのセット・リセット処理は@BeforeEach / @AfterEachメソッド内で行う

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

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

  • 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)を使っていたので、理解を深めるいい機会となりました。

参考資料