14

全部,

我正在尝试在一些古老的 java 代码中进行一些单元测试(没有接口,没有抽象等)

这是一个使用 ServletContext 的 servlet(我假设它是由 Tomcat 设置的),并且它在 web.xml/context.xml 文件中设置了数据库信息。现在,我已经想出了如何制作一个 Fake ServletContext,但是代码有

 InitialContext _ic = new InitialContext();

到处都是(所以更换它是不可行的)。我需要找到一种方法来使默认 InitialContext() 能够在_ic.lookup(val)不引发异常的情况下执行此操作。

我假设 context.xml 以某种方式被加载,但是这种魔法是如何工作的,我正在画一个空白。有人有想法么?

4

7 回答 7

37

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


} 

而这个 impl 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;
    }
}

UseInitialContext在junit测试中创建一个实例

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

在命令行输出上This is my object!!(易于在 eclipse 中设置)。我喜欢Mockito的嘲笑和存根。如果您处理大量遗留代码,我还推荐 Micheal Feather 的《有效处理遗留代码》。这完全是关于如何在程序中找到接缝,以便隔离特定的部分进行测试。

于 2012-04-06T18:10:09.450 回答
6

这是我为单元测试设置初始上下文的解决方案。首先,我将以下测试依赖项添加到我的项目中:

<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 方法。

于 2012-04-06T16:14:32.863 回答
4

尝试设置系统变量之前:

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

于 2012-04-06T16:04:59.510 回答
3

您可以使用PowerMock模拟 InitialContext 的构造并控制其行为。此处记录了构造函数模拟。

PowerMock 测试可能非常混乱和复杂,重构通常是更好的选择。

于 2012-04-06T15:33:26.667 回答
1

今天我遇到了同样的问题(我们不能使用 PowerMock)并以这种方式解决了它:

  1. 不要在构造函数中查找,因此当您在对象上调用 @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);
    ...
}
于 2016-11-04T16:31:20.387 回答
0

你考虑过模仿吗?

这很简单:

InitialContext ctx = mock(InitialContext.class);

顺便说一句,如果您选择使用模拟,我建议您也阅读这篇文章:http ://martinfowler.com/articles/mocksArentStubs.html

于 2012-04-06T16:22:31.390 回答
0

一个穷人的独立实现,不使用外部库:

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在方法的覆盖中使用 alookup来返回val传递给_ic.lookup(val)整个遗留程序的每个不同值的预期值。

于 2019-10-03T14:29:57.493 回答