簡體   English   中英

如何使用默認構造函數偽造 InitialContext

[英]How to fake InitialContext with default constructor

全部,

我正在嘗試在一些陳舊的 java 代碼中進行一些單元測試(沒有接口,沒有抽象等)

這是一個使用 ServletContext 的 servlet(我假設它是由 Tomcat 設置的)並且它在 web.xml/context.xml 文件中設置了數據庫信息。 現在,我已經想出了如何制作一個 Fake ServletContext,但是代碼有

 InitialContext _ic = new InitialContext();

到處都是(所以更換它是不可行的)。 我需要找到一種方法來使默認的 InitialContext() 能夠執行_ic.lookup(val)而不會引發異常。

我假設有某種方式可以加載 context.xml,但是這個魔法是如何工作的,我正在畫一個空白。 誰有想法?

利用InitialContext使用 SPI 來處理其創建這一事實。 您可以通過創建javax.naming.spi.InitialContextFactory的實現並通過系統屬性javax.naming.factory.initial ( Context.INTITIAL_CONTEXT_FACTORY ) 將其傳遞給您的測試來掛鈎其生命周期。 它比聽起來簡單。

鑒於此 class:

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!! (很容易在日食中設置)。 我喜歡Mockito用於 mocking 和存根。 如果您處理大量遺留代碼,我還推薦 Micheal Feather 的Working Effectively with Legacy Code 這一切都是關於如何在程序中找到接縫,以便隔離特定的部分進行測試。

這是我為單元測試設置 Inintial Context 的解決方案。 首先,我將以下測試依賴項添加到我的項目中:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>catalina</artifactId>
  <version>6.0.33</version>
  <scope>test</scope>
</dependency>

然后我用下面的代碼創建了一個 static 方法:

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);
}

最后,在我的每個測試 class 中,我添加了一個調用 setupInitialContext 的 @BeforeClass 方法。

之前嘗試設置系統變量:

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

您可以使用PowerMock來模擬 InitialContext 的構造並控制其行為。 此處記錄了構造函數 Mocking。

PowerMock 測試可能非常混亂和復雜,重構通常是更好的選擇。

今天我遇到了同樣的問題(我們不能使用 PowerMock)並以這種方式解決了它:

  1. 不要在構造函數中查找,因此當您在 object 上調用 @InitMock 時,構造函數還不需要上下文。

  2. 創建一個在需要時檢索服務 bean 的方法,如“getService().serviceMethod(param, param...)”:

 /* 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); ... }

你考慮過 mockito嗎?

很簡單:

InitialContext ctx = mock(InitialContext.class);

順便說一句,如果您選擇使用模擬,我也建議您閱讀這篇文章: http://martinfowler.com/articles/mocksArentStubs.html

不使用外部庫的窮人獨立實現:

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*/
        ...

您可以在lookup方法的覆蓋中使用一個switch ,以返回整個遺留程序中傳遞給_ic.lookup(val)的每個不同val值的預期值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM