web-dev-qa-db-ja.com

Mockito Spy-コンストラクターを呼び出す前のスタブ

私はオブジェクトをスパイしようとしていて、コンストラクターが呼び出す前に、コンストラクターによって呼び出されるメソッドをスタブ化したいと思います。
私のクラスは次のようになります:

public class MyClass {
    public MyClass() {
         setup();
    }

    public void setup() {

    }
}

Setupメソッドは呼び出さないでください。さて、どのように私はこの方法をスパイするのですか(そしてそれが何もしないようにスタブをセットアップします)
これはメソッドのモックで問題なく動作しますが、MyClassを単体テストしたいので、非常に他のメソッドが必要になります。


何もしないようにsetupメソッドをスタブする必要がある理由:
私はレゴロボット(lejos)をプログラミングしていて、ロボットが動作するために必要なコードをセットアップしました。ただし、TinyVM(ロボットにインストールされているVM)の外部で呼び出すと、Javaが正しく初期化されていないため、VMがクラッシュします(テストがPC)。単体テストでは、設定は重要ではありません。
クラス/メソッドのセットアップコールの一部はパブリックスタティックファイナル変数であるため、スタブできません。

18
Matt3o12

提案をありがとう、しかしそれは少し複雑すぎました。
クラスを拡張してセットアップメソッドを上書きすることで、メソッドのモックを作成しました。このようにして、デフォルトのコンストラクターはセットアップの実装を呼び出さず、代わりに上書きされたメソッドを呼び出します。
これがコードです:

// src/author/MyClass.Java

public class MyClass {
    public MyClass() {
        setup();
    }

    protected void setup() {
        throw new Exception("I hate unit testing !");
    }

    public boolean doesItWork() {
        return true;
    }
}

// test/author/MyClass.Java

public class MyClassTest {
    private class MockedMyClass extends MyClass {
        @Override
        protected void setup() {

        }
    }

    private MyClass instance;

    @Before
    public void setUp() { // Not to be confusing with `MyClass#setup()`!
        instance = new MockedMyClass();
    }

    @Test
    public void test_doesItWork() {
        assertTrue(instance.doesItWork());
    }

}

MyTestのセットアップメソッドがテスト以外のサブクラスによって呼び出されたり上書きされたりしたくない場合は(他の開発者がセットアップメソッドを使用することで非常にひどく混乱する可能性があるため)、可視性をデフォルトに変更すると、クラスのみが可能になりますセットアップを呼び出す。


もっと簡単な方法がある場合、私は私のソリューションで100%のコンテンツではないので、質問に答えてください。

9
Matt3o12

質問に直接回答するには、Mockitoを使用して、コンストラクターから呼び出されたメソッドをスタブすることはできません。 Mockitoはモックを開始する前にクラスのインスタンスを必要としますが、テスト用のインスタンスを作成する方法をまだ与えていません。

より一般的には、Effective Javaitem 17 コンストラクターからオーバーライド可能なメソッドを呼び出さないでください で言及されています。その場合、たとえば、finalフィールドを参照するサブクラスでオーバーライドを提供できますが、finalフィールドが設定される前に実行されます。ここで問題が発生することはおそらくありませんが、Javaの悪い習慣です。

幸い、コードを再構築して、これを非常に簡単に行うことができます。

public class MyClass {
  public MyClass() {
    this(true);
  }

  /** For testing. */
  MyClass(boolean runSetup) {
    if (runSetup) {
      setup();
    }
  }

  /* ... */
}

さらに明確にするために、1つのパラメーターのMyClassコンストラクターをプライベートにして、public staticファクトリメソッド:

/* ... */
  public static MyClass createForTesting() {
    return new MyClass(false);
  }

  private MyClass(boolean runSetup) {
/* ... */

一部の開発者は、主にテストに使用されるメソッドでコードを記述することは悪い習慣だと考えていますが、コードの設計はあなたが担当し、テストは、対応する必要があることを絶対に知っている数少ないコンシューマーの1つであることを覚えておいてください。 「本番」コードで明示的なテストセットアップを回避することは依然として良い考えですが、テストのために追加のメソッドまたはオーバーロードを作成すると、通常、コード全体がよりクリーンになり、テストカバレッジと可読性が大幅に向上します。

  1. PowerMockを使用します。

  2. ライブラリをインポートしたら、呼び出さないインスタンスメソッドでモックしたいクラスを操作するように設定します。

そのようです:

@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
  1. テストの開始時にメソッドを抑制します。

そのようです:

suppress(method(Myclass.class, "setup"));
  1. テストで、必要に応じてsetup()メソッドの動作をカスタマイズします。

そのようです:

doAnswer(new Answer<Void>() {
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
           // code here
           return null;
      }
 }).when(Myclass.class, "setup");
10
fragorl