web-dev-qa-db-ja.com

Mockitoでnew()呼び出しを含むクラスをテストする

LoginContext()をインスタンス化するnew()呼び出しを含むレガシークラスがあります。

public class TestedClass {
  public LoginContext login(String user, String password) {
    LoginContext lc = new LoginContext("login", callbackHandler);
  }
}

インスタンス化する前にJAASセキュリティを設定する必要があるため、Mockitoを使用してこのクラスをテストし、LoginContextをモックしたいと思いますが、login()メソッドを変更してLoginContextを外部化せずにそれを行う方法はわかりません。 Mockitoを使用してLoginContextクラスをモックすることは可能ですか?

56
bwobbones

将来的には Eran Harelの答え (モック可能なnewを工場に移動するリファクタリング)をお勧めします。しかし、元のソースコードを変更したくない場合は、非常に便利でユニークな機能を使用してください:spiesドキュメント から:

実際のオブジェクトのスパイを作成できます。スパイを使用すると、realメソッドが呼び出されます(メソッドがスタブ化されていない場合)。

たとえば、レガシーコードを扱う場合は、実際のスパイを慎重に、ときどき使用する必要があります。

あなたの場合、あなたは書くべきです:

TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);
51

私はすべてEran Harelのソリューションに賛成しています。それが不可能な場合、Tomasz Nurkiewiczのスパイに関する提案は素晴らしいものです。ただし、どちらも当てはまらない状況があることに注意してください。例えば。 loginメソッドが少し「beefier」だった場合:

public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
}

...これは、新しいLoginContextの初期化を独自のメソッドに抽出し、前述の解決策の1つを適用するためにリファクタリングできない古いコードでした。

完全を期すために、3番目の手法に言及する価値があります。 PowerMock を使用して、new演算子が呼び出されたときにモックオブジェクトを注入します。ただし、PowerMockは特効薬ではありません。それはモックするクラスにバイトコード操作を適用することで機能しますが、テストされたクラスがバイトコード操作またはリフレクションを採用している場合は危険な練習になる可能性があり、少なくとも私の個人的な経験から、テストにパフォーマンスヒットをもたらすことが知られています。繰り返しますが、他のオプションがない場合、唯一のオプションは良いオプションでなければなりません:

@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {

    @Test
    public void testLogin() {
        LoginContext lcMock = mock(LoginContext.class);
        whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
        TestedClass tc = new TestedClass();
        tc.login ("something", "something else");
        // test the login's logic
    }
}
32
Mureinik

ファクトリを使用して、ログインコンテキストを作成できます。その後、ファクトリをモックして、テストに必要なものを返すことができます。

public class TestedClass {
  private final LoginContextFactory loginContextFactory;

  public TestedClass(final LoginContextFactory loginContextFactory) {
    this.loginContextFactory = loginContextFactory;
  }

  public LoginContext login(String user, String password) {
    LoginContext lc = loginContextFactory.createLoginContext();
  }
}

public interface LoginContextFactory {
  public LoginContext createLoginContext();
}
21
Eran Harel
_    public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
  }
_

-テストクラス:

_    @RunWith(PowerMockRunner.class)
    @PrepareForTest(TestedClass.class)
    public class TestedClassTest {

        @Test
        public void testLogin() {
            LoginContext lcMock = mock(LoginContext.class);
            whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
//comment: this is giving mock object ( lcMock )
            TestedClass tc = new TestedClass();
            tc.login ("something", "something else"); ///  testing this method.
            // test the login's logic
        }
    }
_

TestLogin()から実際のメソッドtc.login ("something", "something else");を呼び出す場合{-このLoginContext lcはnullに設定され、lc.doThis();の呼び出し中にNPEをスローします

5
sunjavax

私が知っていることではありませんが、テストしたいTestedClassのインスタンスを作成するときにこのようなことをするのはどうですか:

TestedClass toTest = new TestedClass() {
    public LoginContext login(String user, String password) {
        //return mocked LoginContext
    }
};

別のオプションは、Mockitoを使用してTestedClassのインスタンスを作成し、模擬インスタンスがLoginContextを返すようにすることです。

3
Kaj

テスト対象のクラスを変更できる状況で、バイトコードの操作を避けたり、物事を高速にしたり、サードパーティの依存関係を最小限に抑えることが望ましい場合、ファクトリを使用してnewを抽出します操作。

public class TestedClass {

    interface PojoFactory { Pojo getNewPojo(); }

    private final PojoFactory factory;

    /** For use in production - nothing needs to change. */
    public TestedClass() {
        this.factory = new PojoFactory() {
            @Override
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
    }

    /** For use in testing - provide a pojo factory. */
    public TestedClass(PojoFactory factory) {
        this.factory = factory;
    }

    public void doSomething() {
        Pojo pojo = this.factory.getNewPojo();
        anythingCouldHappen(pojo);
    }
}

これを配置すると、Pojoオブジェクトでの呼び出しのテスト、アサート、検証が簡単になります。

public  void testSomething() {
    Pojo testPojo = new Pojo();
    TestedClass target = new TestedClass(new TestedClass.PojoFactory() {
                @Override
                public Pojo getNewPojo() {
                    return testPojo;
                }
            });
    target.doSomething();
    assertThat(testPojo.isLifeStillBeautiful(), is(true));
}

このアプローチの唯一の欠点は、TestClassに余分なパラメーターで複製する必要がある複数のコンストラクターがある場合に発生する可能性があります。

SOLID=.

public class Pojo {

    interface PojoFactory { Pojo getNewPojo(); }

    public static final PojoFactory productionFactory = 
        new PojoFactory() {
            @Override 
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
1
Adam