LoginSignup
9
13

More than 5 years have passed since last update.

JUnit 4.12 の階層構造と JMockit を組み合わせて使う

Last updated at Posted at 2015-10-07

JUnit 4.12 で、 Enclosed よりも柔軟な階層構造の定義がサポートされるようになりました。

こいつと JMockit を組み合わせて使う方法です。

実装

依存関係

build.gradle
dependencies {
    testCompile 'org.jmockit:jmockit:1.19'
    testCompile 'junit:junit:4.12'
    testCompile 'de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1'
}

テストクラス

SampleTest.java
package sample.junit;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import mockit.Verifications;

// ★runner に HierarchicalContextRunner.class を指定
@RunWith(HierarchicalContextRunner.class)
public class SampleTest {

    private static final String UN_USED = null;

    // ★static なインナークラスを定義する
    public static class TestBase {

        protected Sample sample;

        // ★共通で利用するモックは、このクラスにまとめて宣言する
        @Mocked protected Hoge hoge;
        @Mocked protected Fuga fuga;
        @Mocked protected Piyo piyo;

        @Before
        public void setup() {
            sample = new Sample();
        }
    }

    // ★TestBase を継承して非 static なインナークラスを定義する
    public class サンプルメソッドのテスト extends TestBase {

        @Test
        public void 渡した値がそのまま返される() throws Exception {
            // setup
            String arg = "xyz";

            // exercise
            String actual = sample.method(arg);

            // verify
            assertThat(actual, is(arg));
        }

        public class hogeのメソッドがtrueを返す場合 extends TestBase {

            @Before
            public void setupMock() {
                new NonStrictExpectations() {{
                    hoge.method(); result = true;
                }};
            }

            @Test
            public void fugaのメソッドが呼ばれること() throws Exception {
                // exercise
                sample.method(UN_USED);

                // verify
                new Verifications() {{
                    fuga.method(); times = 1;
                }};
            }
        }

        public class hogeのメソッドがfalseを返す場合 extends TestBase {

            @Test
            public void piyoのメソッドが呼ばれること() throws Exception {
                // exercise
                sample.method(UN_USED);

                // verify
                new Verifications() {{
                    piyo.method(); times = 1;
                }};
            }
        }
    }
}

各実装

Hoge.java
package sample.junit;

public class Hoge {

    public boolean method() {
        return false;
    }
}
Fuga.java
package sample.junit;

public class Fuga {
    public void method() {
    }
}
Piyo.java
package sample.junit;

public class Piyo {
    public void method() {
    }
}
Sample.java
package sample.junit;

public class Sample {

    private Hoge hoge = new Hoge();
    private Fuga fuga = new Fuga();
    private Piyo piyo = new Piyo();

    public String method(String arg) {
        if (this.hoge.method()) {
            this.fuga.method();
        } else {
            this.piyo.method();
        }

        return arg;
    }
}

ポイント

テストランナーに HierarchicalContextRunner を使用する

SampleTest.java

import de.bechte.junit.runners.context.HierarchicalContextRunner;
...

@RunWith(HierarchicalContextRunner.class)
public class SampleTest {

...
  • 階層構造が使えるようにするためには、テストランナーに HierarchicalContextRunner.class を指定する。

共通利用するモックをインナークラスで定義する

SampleTest.java
    ...

    public static class TestBase {

        protected Sample sample;

        @Mocked protected Hoge hoge;
        @Mocked protected Fuga fuga;
        @Mocked protected Piyo piyo;

        @Before
        public void setup() {
            sample = new Sample();
        }
    }

    ...
  • 共通利用するモックは、インナークラスにまとめて定義する。
  • このクラスには普通テストメソッドを宣言しない。
  • テストメソッドが存在しないインナークラスが存在すると、実行時に JUnit がエラーになる。
  • よって、このインナークラスはテスト対象にならないように宣言しないといけない
  • テスト対象にしないためには、以下のいずれかの条件を満たすようにすればいい。
    • public なクラスにする。
    • static なクラスにする。

階層を表すインナークラス

SampleTest.java
    ...

    public class サンプルメソッドのテスト extends TestBase {

    ...
  • 階層を表すクラスは、非 staticpublic になるようにして、先ほどの共通インナークラス(TestBase)を継承して作成する。

ダメパターン

最初に試してダメだったパターン。

共通利用するモックを、インナークラスではなく SampleTest のフィールドとして定義する。

SampleTest.java
package sample.junit;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import mockit.Verifications;

@RunWith(HierarchicalContextRunner.class)
public class SampleTest {

    private static final String UN_USED = null;

    private Sample sample;

    @Mocked private Hoge hoge;
    @Mocked private Fuga fuga;
    @Mocked private Piyo piyo;

    @Before
    public void setup() {
        sample = new Sample();
    }

    public class サンプルメソッドのテスト {

        ...

        public class hogeのメソッドがtrueを返す場合 {

            @Before
            public void setupMock() {
                new NonStrictExpectations() {{
                    hoge.method(); result = true;
                }};
            }

            @Test
            public void fugaのメソッドが呼ばれること() throws Exception {
                // exercise
                sample.method(UN_USED);

                // verify
                new Verifications() {{
                    fuga.method(); times = 1;
                }};
            }
        }

        ...
    }
}
実行結果
java.lang.IllegalStateException: Missing invocation to mocked type at this point; please make sure such invocations appear only after the declaration of a suitable mock field or parameter
    at sample.junit.SampleTest$サンプルメソッドのテスト$hogeのメソッドがtrueを返す場合$1.<init>(SampleTest.java:51)
    at sample.junit.SampleTest$サンプルメソッドのテスト$hogeのメソッドがtrueを返す場合.setupMock(SampleTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        ...

インナークラスで @Mocked でアノテートしたフィールドを参照したときに、モックとして認識されていない模様。

参考

9
13
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
9
13