LoginSignup
11
10

More than 5 years have passed since last update.

JMockit1.4→1.30へのアップデート時にコケたことまとめ

Last updated at Posted at 2017-01-17

Java8u74→8u102ぐらいに上げた際に、どうやらJMockitも上げなきゃならんってことになりましたが、
使ってるJMockitが古すぎて、コンパイルエラーになるやら、実行時エラーになるやらわりとてんやわんやだったので、
備忘録としてまとめることにしました。
※随時更新中

環境

Java8u102(8u74でもたぶん動くかと)
maven3系

change log

だいたい書いてあるけど、実際にどう書けばいいのかわからないのもありつつ
JMockit Development history

主に参考にさせていただいたサイト

  • JMockit使い方メモ だいたい載ってて、しかも、詳しく書き方も載っててマジ感謝です。

まずはライブラリのバージョンをアップ

JMockit 1.4

pom.xml
        <dependency>
            <groupId>com.googlecode.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>

JMockit 1.30

groupすら変わってることにビビります。(よくあるけどさ・・・)
※追記:ご指摘いただきました。昔から2つグループがあったらしいです。が、統合されたんですかね?

pom.xml
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.30</version>
            <scope>test</scope>
        </dependency>

1. コンパイルエラーを解消する

NonStrictExpectationsをExpectationsに変更する

Version 1.23でDeprecated、Version 1.25で削除になってます。

Version 1.23 (Apr 24, 2016):
Deprecated the NonStrictExpectations class. Existing tests should use Expectations instead.

Version 1.25 (Jun 26, 2016):
Removed the NonStrictExpectations class, which was deprecated in version 1.23.

とりあえず、コンパイルエラーをなんとかしたいので、盲目的に変えていきます。

NonStrictExpectationsの使い方は以下のリンクでまとめてくださってます(もう使えないけど。。)
jMockitがいいよ Verification編

NonStrictExpectationsとExpectationsとの違いは

  • Expectations 実行順序、実行回数などすべて記録。かなり厳密。
  • NonStrictExpectations ちょっとゆるめ。実行されてもされなくてもスルーされる。

みたいな違いがあるらしいです。詳しくは以下のリンクでわかりやすくまとめてくださってます。感謝。
JMockit使い方メモ NonStrictExpectations
テスト実行時に落ちるので、これは後ほど修正します。

@Mocked(methods="readLine")とかアノテーション内の引数を指定してるやつ

Version 1.5でDeprecated 、Version 1.6で『methods』『inverse』属性は削除されてます。

Version 1.5 (Oct 20, 2013):
Deprecated the "methods" and "inverse" attributes of @Mocked. Existing uses like @Mocked(methods = {"method1", "method2"}) should simply be rewritten as @Mocked({"method1", "method2"}) (using the default value attribute). As for the inverse attribute, specify what is to be mocked instead.
Version 1.6 (Dec 23, 2013):
Removed the "methods" and "inverse" attributes of the @Mocked annotation, which were deprecated in release 1.5. Tests still using "@Mocked(methods = {...}) should instead use the default "value" attribute.

今度から@Mocked(methods = {...})使ってね、と言ってるわりにはこの方法もVersion 1.19で削除されちゃいます。悲しい。

Version 1.19 (Aug 23, 2015):
Removed the "value" attribute of @Mocked, which was deprecated in release 1.17. Existing tests using "@Mocked({"someMethod", "anotherMethod", ...})" should simply omit the attribute, or use partial mocking as applied with new Expectations(objToPartiallyMock) { ... }, or apply a MockUp class.

Expectationsの引数で書く方法は、その後もいろいろゴニョゴニョあって、使えるんだか使えないんだかわからないので、
おとなしくMockUpに書き換えます。

修正前.java
@Test
public void test(@Mocked(methods="readLine") final BufferedReader br) {
        new NonStrictExpectations() {
            {
                br.readLine();
                result = new IOException();
            }
        };
}
修正後.java
@Test
public void test() { // MockUp<T>で定義するので、引数は削除する。あるとエラーになる
        new MockUp<BufferedReader>() {
            @Mock
            public String readLine() throws IOException{
                throw new IOException();
            }
        };  
}

ちなみに、BufferedReaderのこの場合だと、@Mocked@Injectableに変えても良さそう(というか、どちらも同じ結果になった)
どっちがいいのかなぁ。
詳しくはこちら→JMockit はじめの一歩 @Injectable

setField, invokeはDeencapsulationのstaticメソッドに変更する

setField, invokeとかprivateなやつらに対して操作を行うのは、Invocationsクラスだったようなんですが、Version 1.6にて削除されたようです。

Version 1.6 (Dec 23, 2013):
Removed all Reflection-based utility methods from the Invocations class (the base for Expectations and Verifications), in favor of using the static methods of identical signatures in the Deencapsulation class. This change in the API will not require any changes to test code, except that test classes using these methods should now include the following "static import": import static mockit.Deencapsulation.*;

修正前.java
setField(hoge, "aaaa", "aaaa");
invoke((hoge, "privateMethod");
変更後.java
Deencapsulation.setField(hoge, "aaaa", "aaaa");
Deencapsulation.invoke((hoge, "privateMethod");

ちなみに、private static finalのフィールドにセットしようとすると、Can not set static finalって怒られる場合があるので、その場合は後述Deencapsulation.setFieldの挙動が変わったらしいを参考に。

2. テストでエラーになるものを修正する

Missing 1 invocation to: ~ Caused by: Missing invocations

これでコケるやつはNonStrictExpectationsをExpectationsに変更するの影響です。
このメソッド呼ばれてねーよ!って怒っております。サーセン。
メソッドの記録が厳密になった故のエラーですね。
回避策としては、メソッドの最小期待実行回数を0にしちゃいます。
ちなみに、呼ばれていないことを明示的に示したい場合は、maxTimes=0でチェックですれば良さそう。

HogeTest.java
        new Expectations() {{
            hoge.method(); minTimes=0;
            hoge.method2(); maxTimes=0;
        }};

まあ、これをつけると、呼ばれたか呼ばれてないかわからなくなるので、その辺はVerificationsでチェックするようにしたらいいんじゃないかなと。
※追記:maxTimes=0;でチェックできるじゃんと気づきました。

EnumのMockの書き方が変わった?

こんな書き方してるやつが、NullPointerExceptionで落ちるようになった。

修正前.java
@Test
public void test(@Mocked final HogeEnum hogeEnum) {

    new Expectations() {

        @Mocked
        HogeEnum hogeEnum;

        {
            hogeEnum.getHoge();
            result = "hogehoge";
        }
    };

どうやらMockUpに書き換えれば良いらしい。
How to mock this in an Enum class using jmockt?

修正後.java
@Test
public void test() { // MockUpで書くので、引数は消す
    new MockUp<HogeEnum>() {
        @Mock
        public String getHoge() {
            return "/NOTHING/DATA.mf";
        }
    };
}

NonStrictExpectations→Expectationsに変えたことによるMockインスタンスの挙動変更?

こんな書き方してたやつをNonStrictExpectations→Expectationsに変えたら、failになってしまった。

変更前.java
    @Test
    public void test() throws Exception {
        final Hoge hoge1 = new Hoge("hogehoge");
        new NonStrictExpectations() {
            {
                test.getHoge("hogehoge");
                result = hoge1;
            }
        };

        assertThat(test.getHoge("hogehoge"), hoge1);

        assertThat(test.getHoge("fugafuga"), null);  // ここでnullにはならない!
    }

デバックしてみたら、Hogeのインスタンス自体は作られてしまってた。(valueとかはnullなんだけど)
ここ見ると、JMockit使い方メモ モック化されたオブジェクトのメソッドが返す値 とあるので、挙動通りではあるのですが、引数によって返すオブジェクト変えたいっていうか、ある引数以外のときはnull返したいんですよ。
※追記:nullを返したい場合は明示的に指定しないとだめらしい。ここに書いてました。→ JMockit使い方メモ モックが返す値を null にしたい場合
※2017/01/20追記:7tsunoさんよりコメントいただきました。きっちりやるならDelegate使うのが良いですね。詳しくは→Delegateでのマッチング

修正方法

引数違う場合のresultを明示的に指定する

修正案.java
    @Test
    public void test() throws Exception {
        final Hoge hoge1 = new Hoge("hogehoge");
        new Expectations() {
            {
                test.getHoge("hogehoge");
                result = hoge1;
                test.getHoge("fugafuga");
                result = null;
            }
        };

        assertThat(test.getHoge("hogehoge"), hoge1);

        assertThat(test.getHoge("fugafuga"), null);
    }

Deencapsulation.setFieldの挙動が変わったらしい

private static final なフィールドの書き換えが1.4では動いてたんですが、どうやら挙動が変わったらしく、java.lang.RuntimeException: java.lang.IllegalAccessException: Can not set static final ~ が発生するようになりました。(セットする対象はプリミティブではないです。セットしたのはMockオブジェクト。)
Deencapsulation.setField() is broken for JDK8 after I upgrade to v1.24

Ok, I updated our tests to use a MockUp instead. This is cleaner anyway.

え、MockUp 使えってことですか?
というわけで、static 初期化ブロックをモック化するを参考に使って書き直してみました。

Hoge.java
// Applicationは自作クラスです
private final static Application application = Application.getInstance();
修正前.java
Hoge hoge = new Hoge();
Deencapsulation.setField(Hoge.class , application);
修正後.java
        new MockUp<Hoge>() {
            @Mock
            void $clinit() {
                Deencapsulation.setField(Hoge.class ,hoge);
            }
        };

staticブロックで宣言してなくても$clinit()で代入出来るのか。謎。

Deencapsulation.invokeも挙動が変わったみたい

invokeも一部駄目でした。動くやつと動かない差がわからない。。
※Expectations内でinvokeしてるやつは、MockUpで書かないと駄目っぽいです。ただいま確認中
これもMockUp使うと動きました。MockUp強すぎ。

PrivateHoge.java
// こんなメソッドがあったとする
private Hoge getHoge() {
  return hoge;
}
修正前.java
new Expectations() {{
  Deencapsulation.invoke(privateHoge, "getHoge");
}};

MockUp使って書き直し

修正後.java
        new Expectations() {{
            new MockUp<PrivateHoge>() {
                @Mock
                Hoge getHoge() {
                    return hogehoge;
                }
            };
        }};

MockUp内のMock化したメソッドなら、Deencapsulation.invokeも使えるみたいです。
NullPointerって言ってるから、明示的にしてからinvokeしろってことなのかなぁ

修正案その2.java
        new Expectations() {{
            new MockUp<PrivateHoge>() {
                @Mock
                Hoge getHoge() {
                    Deencapsulation.invoke(privateHoge, "getHoge");
                }
            };
        }};

動的部分モック化の書き方が変わった模様

Expectations内で@Mockedインスタンス宣言してたやつらがnullを返すようになりました。
ちなみに部分モックのことをPartial Mockingと言うらしいです。へぇー
jMockit Verifications 部分モッキングとか

動かなくなったやつ.java
        new Expectations(patternManager) {
            @Mocked Hoge hoge;
            {
                System.out.println(hoge);
                System.out.println(hoge.hoge);

修正案

  1. 単体テストのインスタンスフィールドに変える
  2. 単体テストの実行メソッドの引数で@Mocked使って宣言する→ テストメソッドの引数で定義する

のどちらかで。

コンストラクタの戻り値は返せなくなった

コンストラクタの戻り値は指定できない

これが結構難儀で格闘中

代替案①

Accessing the invocation context にあるように$initメソッドでコンストラクタと同じ処理が出来るっぽい。シグニチャ合わせればよい。

HogeTest.java
new MockUp<File>() {
      @Mock
      void $init(File file, String path)
      {
        // new File(file, path) のコンストラクタをモック化出来る
      }

ちなみに、Invocationを先頭の引数に加えると、モックインスタンスも取れるっぽい。

SystemクラスとかネイティブクラスもMockUpで書けばいいらしい

その他、参考にさせていただいたサイト様

11
10
2

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
11
10