繁体   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