web-dev-qa-db-ja.com

PowerMock:プライベートな静的最終変数のモックアウト、具体例

このテストに合格するために必要な絶対的な最小限のモックは何ですか?

コード:

class PrivateStaticFinal {
    private static final Integer variable = 0;
    public static Integer method() { return variable + 1; }
}

テスト:

@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivateStaticFinal.class)
class PrivateStaticFinalTest {
    @Test
    public void testMethod() {
        //TODO PrivateStaticFinal.variable = 100
        assertEquals(PrivateStaticFinal.method(), 101);
    }
}

関連: テストクラスのプライベートな静的最終変数のモック (明確な答えなし)

22
sam boosalis

免責事項:さまざまなスレッドを何度も探し回った後、答えを見つけました。それは実行できますが、一般的なコンセンサスは非常に安全ではないが、ユニットテストでのみこれをどのように行うかを見て、あなたはそれらのリスクを受け入れると思います:)


答えはモッキングではありません。ほとんどのモッキングではファイナルにハックできないためです。答えはもう少し「ハッキング」です。ここでは、Javaが呼び出しているときにコアJava.lang.reflect.FieldおよびJava.lang.reflect.Modifierクラス(リフレクション)。 この回答 を見ると、問題を解決するためにモックを作成する必要なく、テストの残りの部分をまとめることができました。

その答えの問題は、NoSuchFieldExceptionを変更しようとしたときにvariableにぶつかっていたことです。そのためのヘルプは、非公開で公開されていないフィールドへのアクセス方法に関する 別の投稿 にあります。

反射/フィールド操作の説明:

Mockingはfinalを処理できないため、代わりに私たちがやることは、フィールド自体のルートへのハッキングです。 Field操作(リフレクション)を使用する場合、クラス/オブジェクト内の特定の変数を探しています。 Javaが見つかると、その「修飾子」を取得します。これは、変数にfinalstaticprivatepublicなど。適切な変数を見つけて、アクセス可能であることをコードに伝え、これらの修飾子を変更できるようにします。それを操作するために、その「最終」部分を切り替えてから、値を変更して必要なものに設定できます。

簡単に言うと、変数を変更してそのプロパティを変更できるようにし、finalのプロパティを削除してから、値がfinalではなくなったため変更します。詳細については、 アイデアの出所の投稿をご覧ください

だから、ステップバイステップで、操作したい変数を渡します...

// Mark the field as public so we can toy with it
field.setAccessible(true);
// Get the Modifiers for the Fields
Field modifiersField = Field.class.getDeclaredField("modifiers");  
// Allow us to change the modifiers
modifiersField.setAccessible(true);
 // Remove final modifier from field by blanking out the bit that says "FINAL" in the Modifiers
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set new value
field.set(null, newValue); 

これをすべて組み合わせて、新しいスーパーアンサーを手に入れましょう。

@RunWith(PowerMockRunner.class)
@PrepareForTest()
class PrivateStaticFinalTest {
    @Test
    public void testMethod(){
      try {
        setFinalStatic(PrivateStaticFinal.class.getDeclaredField("variable"), Integer.valueOf(100));
      } 
      catch (SecurityException e) {fail();}
      catch (NoSuchFieldException e) {fail();}
      catch (Exception e) {fail();}
      assertEquals(PrivateStaticFinal.method(), Integer.valueOf(101));
    }

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        // remove final modifier from field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
}

Update上記の解決策は、静的ブロックで初期化された定数に対してのみ機能します。定数を同時に宣言および初期化すると、コンパイラーはそれをインライン化します。その時点で、元の値への変更は無視されます。

39
Walls