Mockitoはローカルで最終クラスをモックしますが、ジェンキンスで失敗します


11

静的メソッドのユニットテストをいくつか作成しました。staticメソッドは引数を1つだけ取ります。引数の型は最終クラスです。コードに関して:

public class Utility {

   public static Optional<String> getName(Customer customer) {
       // method's body.
   }
}

public final class Customer {
   // class definition
}

そのため、UtilityクラスUtilityTestsについて、このメソッドのテストを記述したテストクラスを作成しましたgetName。ユニットテストフレームワークはTestNGであり、使用されるモックライブラリはMockitoです。したがって、一般的なテストの構造は次のとおりです。

public class UtilityTests {

   @Test
   public void getNameTest() {
     // Arrange
     Customer customerMock = Mockito.mock(Customer.class);
     Mockito.when(...).thenReturn(...);

     // Act
     Optional<String> name = Utility.getName(customerMock);

     // Assert
     Assert.assertTrue(...);
   }
}

何が問題ですか ?

テストはローカルでIntelliJ内で正常に実行されますが、Jenkinsでは失敗します(コードをリモートブランチにプッシュすると、ビルドがトリガーされ、ユニットテストが最後に実行されます)。エラーメッセージは次のようなものです。

org.mockito.exceptions.base.MockitoException:クラスをモック/スパイできませんcom.packagename.Customer Mockitoは次の理由でモック/スパイできません:-最終クラス

何を試しましたか?

解決策を見つけるために少し検索しましたが、解決できませんでした。ここで、最終クラスであることを変更すること許可されていません。これに加えて、可能であれば、デザインをまったく変更しないようにしたいと思います(たとえば、モックしたいメソッドを保持し、Customerクラスがそのインターフェイスを実装していることを示すインターフェイスを作成します)。コメント)。私が試したのは、mockito-finalで言及された2番目のオプションです。これが問題を修正したという事実にもかかわらず、それは他のいくつかのユニットテストを壊します:(、それは明白な方法で修正することができません。Customer

ご質問

だからここに私が持っている2つの質問があります:

  1. そもそもそれはどのように可能ですか?ローカルとJenkinsの両方でテストが失敗するべきではありませんか?
  2. 上記の制約に基づいてこれをどのように修正できますか?

助けてくれてありがとう。


1
私の推測では、enable final構成はワークスペースで機能しますが、実行するとJenkinsこのファイルが見つかりません。Jenkinsファイルを探している場所と、実際にそこにあるかどうかを確認します。

この他のスレッドがリソースディレクトリの下mockitoの設定ファイルを追加することにより、Mockito 2に、最終的なクラスのモックを有効にする方法について説明しstackoverflow.com/questions/14292863/...
ホセTepedinoを

3
処理中のコードで、Customerクラス(ICustomerなど)からインターフェイスを抽出し、それをUtilityクラスで使用することは可能でしょうか。次に、具体的な最終クラスの代わりにそのインターフェースをモックすることができます
Jose Tepedino

@JoseTepedinoこれは有効なポイントです。それは完全に理にかなっており、この問題を克服するためのエレガントな方法です。ただし、別の方法があるのか​​どうか、もっと重要なのは、現在のアプローチがローカルで成功し、ジェンキンスで失敗する理由を理解したいです。
Christos

1
Customerロジックはありますか、それとも単なるデータクラスですか?ゲッターとセッターを持つフィールドの集まりの場合は、インスタンス化できます。
ウィリスブラックバーン

回答:


2

別のアプローチは、「クラスへのメソッド」パターンを使用することです。

  1. メソッドをカスタマークラスから別のクラスに移動します。たとえば、CustomerSomething eg / CustomerFinances(またはそれが責任を持つもの)と言います。
  2. コンストラクターをCustomerに追加します。
  3. これで、CustomerSomethingクラスだけで、Customerをモックする必要はありません。外部の依存関係がない場合も、モックする必要はありません。

これはトピックに関する良いブログです:https : //simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/


1
回答いただきありがとうございます(+1)。私はそれを修正する方法を見つけました(2番目の質問に答えてください)。ただし、IntelliJ内でテストが失敗する理由はまだはっきりしていません。さらに、私はそれをもう再現することができません(IntelliJ内の障害)。これは完全に奇妙です。
Christos

1

そもそもそれはどのように可能ですか?ローカルとJenkinsの両方でテストが失敗するべきではありませんか?

それは明らかに環境固有の一種です。唯一の問題は、違いの原因を特定する方法です。

org.mockito.internal.util.MockUtil#typeMockabilityOf方法を確認して比較することをお勧めしますmockMaker。両方の環境で実際に使用されているものとその理由。

場合mockMakerと同じである-ロードされたクラスを比較IDE-ClientJenkins-Client-彼らは、テスト実行時に任意の違いを持っています。

上記の制約に基づいてこれをどのように修正できますか?

次のコードはOpenJDK 12とMockito 2.28.2を想定して書かれていますが、実際に使用されている任意のバージョンに調整できると思います。

public class UtilityTest {    
    @Rule
    public InlineMocksRule inlineMocksRule = new InlineMocksRule();

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testFinalClass() {
        // Given
        String testName = "Ainz Ooal Gown";
        Client client = Mockito.mock(Client.class);
        Mockito.when(client.getName()).thenReturn(testName);

        // When
        String name = Utility.getName(client).orElseThrow();

        // Then
        assertEquals(testName, name);
    }

    static final class Client {
        final String getName() {
            return "text";
        }
    }

    static final class Utility {
        static Optional<String> getName(Client client) {
            return Optional.ofNullable(client).map(Client::getName);
        }
    }    
}

インラインモックの別のルールを使用して:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class InlineMocksRule implements TestRule {
    private static Field MOCK_MAKER_FIELD;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
            VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);

            MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
            MOCK_MAKER_FIELD.setAccessible(true);

            int mods = MOCK_MAKER_FIELD.getModifiers();
            if (Modifier.isFinal(mods)) {
                modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
            }
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Object oldMaker = MOCK_MAKER_FIELD.get(null);
                MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
                try {
                    base.evaluate();
                } finally {
                    MOCK_MAKER_FIELD.set(null, oldMaker);
                }
            }
        };
    }
}

回答いただきありがとうございます(+1)。私はそれを修正する方法を見つけました(2番目の質問に答えてください)。ただし、IntelliJ内でテストが失敗する理由はまだはっきりしていません。さらに、私はそれをもう再現することができません(IntelliJ内の障害)。これは完全に奇妙です。
Christos

1

同じ引数でテストを実行してください。intellij実行構成がjenkinsと一致するかどうかを確認します。https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html。問題が引数にあることを意味して失敗する場合は、jenkins(端末から)と同じ引数でローカルマシンでテストを実行してみることができます


ファイルorg.mockito.plugins.MockMakerはjenkinsマシンにも存在します。ボットマシンでも同じJVMを使用しています。あなたが指摘した3つを確認します。ありがとう
Christos

Jenkinsで使用されているコマンドを使用して、コンソールからテストを実行しようとしました。同じエラーメッセージで失敗します。IntelliJ内で奇妙なことが起こります。
Christos

実行構成で.idea / workspace.xmlを見てください。これは<component>タグ内にあります。その後、そのxmlをbashコマンドに変換する方法を学ぶことができます
Link182

テストの実行に使用されるjenkins端末コマンドを表示できますか?また、どのパッケージマネージャーを使用していますか。
Link182

ビルドツールとして、Gradleを使用します。
Christos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.