简体   繁体   English

如何使用默认构造函数伪造 InitialContext

[英]How to fake InitialContext with default constructor

All,全部,

I'm trying to do some unit testing in some archaic java code (no interfaces, no abstraction, etc.)我正在尝试在一些陈旧的 java 代码中进行一些单元测试(没有接口,没有抽象等)

This is a servlet that uses a ServletContext (which I'm assuming is set up by Tomcat) and it has database information is set up in the web.xml/context.xml file.这是一个使用 ServletContext 的 servlet(我假设它是由 Tomcat 设置的)并且它在 web.xml/context.xml 文件中设置了数据库信息。 Now, I've figured out how to make a Fake ServletContext, but the code has现在,我已经想出了如何制作一个 Fake ServletContext,但是代码有

 InitialContext _ic = new InitialContext();

all over the place (so it isn't feasible to replace it).到处都是(所以更换它是不可行的)。 I need to find a way to make a default InitialContext() able to do the _ic.lookup(val) without throwing an exception.我需要找到一种方法来使默认的 InitialContext() 能够执行_ic.lookup(val)而不会引发异常。

I'm assuming there is some way that the context.xml is getting loaded, but how that magic works, I'm drawing a blank.我假设有某种方式可以加载 context.xml,但是这个魔法是如何工作的,我正在画一个空白。 Anyone have any ideas?谁有想法?

Take advantage of the fact that InitialContext uses an SPI to handle its creation.利用InitialContext使用 SPI 来处理其创建这一事实。 You can hook into its lifecycle by creating an implementation of javax.naming.spi.InitialContextFactory and passing that to your tests via the system property javax.naming.factory.initial ( Context.INTITIAL_CONTEXT_FACTORY ).您可以通过创建javax.naming.spi.InitialContextFactory的实现并通过系统属性javax.naming.factory.initial ( Context.INTITIAL_CONTEXT_FACTORY ) 将其传递给您的测试来挂钩其生命周期。 It's simpler than it sounds.它比听起来简单。

Given this class:鉴于此 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();
        }
    }


} 

And this impl of InitialContextFactory :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;
    }
}

Creating an instance of UseInitialContext in a junit test with在 junit 测试中创建UseInitialContext实例

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

on the command line outputs This is my object!!在命令行输出This is my object!! (easy to set up in eclipse). (很容易在日食中设置)。 I like Mockito for mocking and stubbing.我喜欢Mockito用于 mocking 和存根。 I'd also recommend Micheal Feather's Working Effectively with Legacy Code if you deal with lots of legacy code.如果您处理大量遗留代码,我还推荐 Micheal Feather 的Working Effectively with Legacy Code It's all about how to find seams in programs in order to isolate specific pieces for testing.这一切都是关于如何在程序中找到接缝,以便隔离特定的部分进行测试。

Here's my solution to setting up the Inintial Context for my unit tests.这是我为单元测试设置 Inintial Context 的解决方案。 First I added the following test dependency to my project:首先,我将以下测试依赖项添加到我的项目中:

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

Then I created a static method with the following code:然后我用下面的代码创建了一个 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);
}

Finally in each of my test class I add an @BeforeClass method which calls setupInitialContext.最后,在我的每个测试 class 中,我添加了一个调用 setupInitialContext 的 @BeforeClass 方法。

Try setting up the system variables before:之前尝试设置系统变量:

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

If you are using JUnit, follow this doc: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit如果您使用的是 JUnit,请遵循此文档: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit

You can use PowerMock to mock construction of the InitialContext and control its behavior.您可以使用PowerMock来模拟 InitialContext 的构造并控制其行为。 Constructor Mocking is documented here .此处记录了构造函数 Mocking。

PowerMock tests can be quite messy and complicated, refactoring is normally a better option. PowerMock 测试可能非常混乱和复杂,重构通常是更好的选择。

Today I've faced the same problem (we can't user PowerMock) and solved it this way:今天我遇到了同样的问题(我们不能使用 PowerMock)并以这种方式解决了它:

  1. Don't lookup in the constructor so when you invoke @InitMock on the object, the constructor doesn't require the context yet.不要在构造函数中查找,因此当您在 object 上调用 @InitMock 时,构造函数还不需要上下文。

  2. Create a method for retrieving the service bean when needed like "getService().serviceMethod(param, param...)":创建一个在需要时检索服务 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. On the test, set it up:在测试中,设置它:
 @Mock ApplicationService applicationServiceBean; @Mock InitialContext ic; @InjectMocks ApplicationResourceProvider arp; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(ic.lookup(anyString())).thenReturn(applicationServiceBean); ... }

Have you considered mockito ?你考虑过 mockito吗?

It's as easy as:很简单:

InitialContext ctx = mock(InitialContext.class); InitialContext ctx = mock(InitialContext.class);

By the way, should you choose to use mocks i would recommend reading this article as well: http://martinfowler.com/articles/mocksArentStubs.html顺便说一句,如果您选择使用模拟,我也建议您阅读这篇文章: http://martinfowler.com/articles/mocksArentStubs.html

A poor man's standalone implementation using no external libraries:不使用外部库的穷人独立实现:

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

You could use a switch in the override of the lookup method to return the expected value for each different val value passed to _ic.lookup(val) throughout the legacy program.您可以在lookup方法的覆盖中使用一个switch ,以返回整个遗留程序中传递给_ic.lookup(val)的每个不同val值的预期值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM