web-dev-qa-db-ja.com

デフォルトのコンストラクターでInitialContextを偽造する方法

すべて、

いくつかの古風なJavaコード(インターフェイスなし、抽象化なしなど)でユニットテストを実行しようとしています。

これは、ServletContext(Tomcatによって設定されていると想定しています)を使用するサーブレットであり、データベース情報がweb.xml /context.xmlファイルに設定されています。今、私は偽のServletContextを作成する方法を理解しましたが、コードには

_ InitialContext _ic = new InitialContext();
_

いたるところにあります(したがって、それを置き換えることは不可能です)。デフォルトのInitialContext()が例外をスローせずに_ic.lookup(val)を実行できるようにする方法を見つける必要があります。

Context.xmlが読み込まれる方法があると思いますが、その魔法がどのように機能するのか、空白を描画しています。誰かアイデアはありますか?

14
Austin

PowerMock を使用して、InitialContextの構築をモックし、その動作を制御できます。コンストラクターのモックが文書化されています ここ

PowerMockテストは非常に面倒で複雑になる可能性があり、通常はリファクタリングの方が適しています。

3
samlewis

InitialContextがSPIを使用して作成を処理するという事実を利用してください。javax.naming.spi.InitialContextFactoryの実装を作成し、それをに渡すことで、ライフサイクルにフックできます。システムプロパティjavax.naming.factory.initialContext.INTITIAL_CONTEXT_FACTORY)を介してテストします。思ったよりも簡単です。

このクラスを考えると:

public class UseInitialContext {

    public UseInitialContext() {
        try {
            InitialContext ic = new InitialContext();
            Object myObject = ic.lookup("myObject");
            System.out.println(myObject);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }


} 

そして、このInitialContextFactoryの実装:

public class MyInitialContextFactory implements InitialContextFactory {

    public Context getInitialContext(Hashtable<?, ?> arg0)
            throws NamingException {

        Context context = Mockito.mock(Context.class);
        Mockito.when(context.lookup("myObject")).thenReturn("This is my object!!");
        return context;
    }
}

を使用したjunitテストでUseInitialContextのインスタンスを作成する

-Djava.naming.initial.factory=initial.context.test.MyInitialContext

コマンドラインでThis is my object!!を出力します(Eclipseで簡単に設定できます)。私は Mockito モックとスタブが好きです。また、多くのレガシーコードを扱う場合は、Micheal Featherの レガシーコードを効果的に使用する をお勧めします。テストのために特定の部分を分離するために、プログラム内の継ぎ目を見つける方法がすべてです。

35
Tim Jones

これが、単体テストの初期コンテキストを設定するための私の解決策です。まず、次のテスト依存関係をプロジェクトに追加しました。

<dependency>
  <groupId>org.Apache.Tomcat</groupId>
  <artifactId>catalina</artifactId>
  <version>6.0.33</version>
  <scope>test</scope>
</dependency>

次に、次のコードを使用して静的メソッドを作成しました。

public static void setupInitialContext() throws Exception {
    System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.Apache.naming.Java.javaURLContextFactory");
    System.setProperty(Context.URL_PKG_PREFIXES, "org.Apache.naming");
    InitialContext ic = new InitialContext();
    ic.createSubcontext("jdbc");
    PGSimpleDataSource ds = new PGSimpleDataSource();
    ds.setDatabaseName("postgres");
    ds.setUser("postgres");
    ds.setPassword("admin");
    ic.bind("jdbc/something", ds);
}

最後に、各テストクラスに、setupInitialContextを呼び出す@BeforeClassメソッドを追加します。

6
Hiro2k

次の前にシステム変数を設定してみてください。

System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.Apache.naming.Java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,
        "org.Apache.naming");
InitialContext ic = new InitialContext();

JUnitを使用している場合は、次のドキュメントに従ってください: https://blogs.Oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit

4

今日、私は同じ問題に直面し(PowerMockを使用できません)、次のように解決しました。

  1. コンストラクターをルックアップしないでください。オブジェクトで@InitMockを呼び出すとき、コンストラクターはまだコンテキストを必要としません。

  2. 「getService()。serviceMethod(param、param ...)」のように、必要に応じてサービスBeanを取得するためのメソッドを作成します。

    /* Class ApplicationResourceProvider */

    /* We can mock this and set it up with InjectMocks */
    InitialContext ic;

    /* method hiding the lookup */
    protected ApplicationService getService() throws NamingException {
        if(ic == null)
            ic = new InitialContext();
        return (ApplicationService)ic.lookup("Java:global/defaultApplicationLocal");
    }
  1. テストでは、次のように設定します。
@Mock
ApplicationService applicationServiceBean;

@Mock
InitialContext ic;

@InjectMocks
ApplicationResourceProvider arp;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    when(ic.lookup(anyString())).thenReturn(applicationServiceBean);
    ...
}
1
periket2000

外部ライブラリを使用しない貧乏人のスタンドアロン実装:

_public class myTestClass {
    public static class TestContext extends InitialContext {
        public TestContext() throws NamingException {
            super(true /*prevents initialization*/);
        }

        static Object someExpectedValue = "the expected string or object instance";

        /*override the method(s) called by the legacy program on _ic, check the parameter and return the wanted value */
        public Object lookup(String name) throws NamingException {
            return name != null && name.equals("theValueOfVal") ? someExpectedValue : null;
        }
    }

    public static class TestInitialContextFactory implements InitialContextFactory {
        public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
            return new TestContext();
        }
    }

    public static void main(String[] args) throws SQLException {
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "the.package.myTestClass$TestInitialContextFactory");
        /*now call the legacy logic to be tested*/
        ...
_

switchメソッドのオーバーライドでlookupを使用して、レガシープログラム全体で_ic.lookup(val)に渡された異なるval値ごとに期待値を返すことができます。 。

0
dlauzon

mockito を検討しましたか?

それは次のように簡単です:

InitialContext ctx = mock(InitialContext.class);

ちなみに、モックを使用することを選択した場合は、この記事も読むことをお勧めします: http://martinfowler.com/articles/mocksArentStubs.html

0