我如何模拟java.net.URL或通过编程设置Springbean来接受构造函数参数?

我如何模拟java.net.URL或通过编程设置Springbean来接受构造函数参数?,java,spring,unit-testing,mocking,Java,Spring,Unit Testing,Mocking,背景 我正在用Java开发一个应用程序套件,通过HTTP post/get调用不同的子系统。在进行单元测试的过程中,我遇到了一个无法直接模拟的问题,因为URL类是final 我现在的位置 由于URL类不能被模拟,我决定创建一个包装类,只公开我正在使用的URL类的方法。你可以在下面找到这个类。在我开始尝试在测试上下文中设置bean之前,这似乎很有效 UrlWrapper import java.io.IOException; import java.net.MalformedURLExceptio

背景
我正在用Java开发一个应用程序套件,通过HTTP post/get调用不同的子系统。在进行单元测试的过程中,我遇到了一个无法直接模拟的问题,因为
URL
类是
final

我现在的位置
由于
URL
类不能被模拟,我决定创建一个包装类,只公开我正在使用的
URL
类的方法。你可以在下面找到这个类。在我开始尝试在测试上下文中设置bean之前,这似乎很有效

UrlWrapper

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class UrlWrapper {

    URL url;

    public UrlWrapper(String spec) throws MalformedURLException{
        url = new URL(spec);
    }

    public URLConnection openConnection() throws IOException{
        return url.openConnection();
    }

}
单元测试方法

@Test
public final void testToMockConstructorInjectedBean()
        throws IOException {
    GenericApplicationContext mockContext = new GenericApplicationContext();

    // Create our mock controller
    UrlWrapper mockUrl = mock(UrlWrapper.class);

    // Set the mock object in the context
    mockContext.refresh();
    mockContext.getBeanFactory().registerSingleton("url", mockUrl);
    UrlWrapper mock = null;
    mock = (UrlWrapper)mockContext.getBean("url"); // <-- Works
    mock = (UrlWrapper)mockContext.getBean("url", "http://google.com/"); // <-- Fails.
}
单元测试之外的样本使用

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ContextProvider {
    private static ApplicationContext appContext;

    public static void setContext(ApplicationContext context) {
        appContext = context;
    }

    public static ApplicationContext getContext() {
        if (appContext == null) {
            appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        }

        return appContext;
    }
}
public boolean post() throws IOException {
    UrlWrapper url;

    // Get my URL from the available context (either 'live' or 'test' context)
    url = (UrlWrapper) ContextProvider.getContext().getBean("url", requestUrl);

    /* More code here omitted for brevity */
}

我建议你看看Mockito的
Spy
。是javadoc,是它的使用教程

在您的例子中,您将创建一个真实URLWrapper对象的间谍,然后执行任何其他需要的交互

@Test
public final void testToMockConstructorInjectedBean()
        throws IOException {
    GenericApplicationContext mockContext = new GenericApplicationContext();

    // Create our mock controller
    UrlWrapper spyUrl = spy(new URLWrapper("http://google.com"));

    // Set the mock object in the context
    mockContext.refresh();
    mockContext.getBeanFactory().registerSingleton("url", spyUrl);
    UrlWrapper spy = null;
    spy = (UrlWrapper)mockContext.getBean("url");
}
在本例中,使用指定的URL创建spy,然后将该对象注册为类的bean

更新

在我看来

url=(UrlWrapper)ContextProvider.getContext().getBean(“url”,requestUrl)


在测试代码中是限制性的。这样的代码意味着需要以特定的方式(静态工厂方法)创建bean,以便能够按照测试的预期创建。我建议您使用已经包含所需行为的bean填充Spring上下文,然后只使用
getBean
而不使用参数我终于找到了如何在运行时使用构造函数参数正确模拟bean的方法。这里的复杂性在于bean的作用域是一个原型,所以我必须在spring中阅读IoC容器。我希望这能帮助那些像我开始走这条路时一样困惑的人

这是我的testApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <bean name="serviceLocator" class="com.maddonkeysoftware.donkeydesktopmonitor.MockBeanProvider">
        <!-- inject any dependencies required by this locator bean -->
    </bean>

    <bean name="url" factory-bean="serviceLocator" factory-method="fetchMockUrl" scope="prototype">
        <constructor-arg value="0"></constructor-arg>
    </bean>

</beans>

我想您的最终结果将是让
openConnection
方法返回一个模拟的
URLConnection
?最终这就是我在这个特定案例中的目的。我还想为将来可能遇到的任何其他测试寻找关于构造函数参数的更一般问题的答案。在我看来,将间谍注册为应用程序上下文的bean可能比注册为Mock更合理。这样你就可以按照你想要的任何方式创建对象,然后只保留你真正想要更改的方法。太好了。谢谢你的指点。我一定会研究一下间谍的事,然后试试看。有没有什么地方可以让我在Spring/Mockito的背景下开始学习间谍?我在谷歌上快速搜索了一下,似乎什么都没有希望。我把它作为答案添加到下面,让未来的读者不必阅读评论就能轻松找到。我不完全确定这是否可行。在代码的其他部分中,我有一个上下文提供程序,它要么通过XML文件创建新上下文,要么使用单元测试提供的上下文。在调用此上下文提供程序的代码中,它调用
getBean(“url”,memberVarString)。我在这里得到的是使用mockContext和
.getBean(“url”)对我来说不是一个选项,除非我转向某种参数注入。如果你更新了你的问题,展示了当前解决方案不起作用的示例,那就好了。谢谢,没问题。我只是在“编辑1”一节下面添加了一些示例代码。我很抱歉第一次没有包括这一点,因为我认为我说得很清楚。不过,感谢您在这一点上的帮助!很高兴有人能从中找到这些东西。代码
getBean(“url”,requestUrl)
假设有一个类,比如
URLWrapperFactoryBean
,它有一个静态方法,在将
requestUrl
作为输入时返回
URLWrapper
的新实例。该测试代码似乎非常有限。我的意见是修改测试代码以允许更灵活的bean创建我说的是
url=(UrlWrapper)ContextProvider.getContext().getBean(“url”,requestUrl)在测试代码中是限制性的。这样的代码意味着需要以特定的方式创建bean,以便能够按照测试的预期创建。我建议您使用已经包含所需行为的bean填充Spring上下文,然后只使用
getBean
而不使用参数
package com.maddonkeysoftware.donkeydesktopmonitor;

import java.util.LinkedList;
import java.util.Queue;

import com.maddonkeysoftware.donkeydesktopmonitor.requests.UrlWrapper;

public class MockBeanProvider {

    private static Queue<UrlWrapper> urlWrapperQueue = new LinkedList<UrlWrapper>();

    private MockBeanProvider() {}

    public static void enqueueMockUrl(UrlWrapper mock){
        urlWrapperQueue.add(mock);
    }

    public Object fetchMockUrl(String args) {
        return urlWrapperQueue.poll();
    }
}
    @Test
public final void baseRequest_PostResponseReturned()
        throws IOException {

    // NOTE: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html
    // Go to the section 4.3.1 and look at the factory for providing custom beans.

    // Create our mock controller
    UrlWrapper mockUrl = mock(UrlWrapper.class);
    URLConnection mockUrlConn = mock(URLConnection.class);

    // set up the mockUrl
    when(mockUrl.openConnection()).thenReturn(mockUrlConn);
    when(mockUrlConn.getInputStream()).thenReturn(IOUtils.toInputStream("Success"));
    when(mockUrlConn.getOutputStream()).thenReturn(new PipedOutputStream(new PipedInputStream()));

    MockBeanProvider.enqueueMockUrl(mockUrl);

    // Set our mock context into our application.
    com.maddonkeysoftware.donkeydesktopmonitor.ContextProvider.setContext(new ClassPathXmlApplicationContext("testApplicationContext.xml"));

    MockBeanProvider p = (MockBeanProvider)ContextProvider.getContext().getBean("serviceLocator");

    // Create our object under test.
    AddImageRequest request = new AddImageRequest();
    request.setRequestUrl("http://testUrl.com");
    boolean result = request.post();

    // Verify that everything was called as expected.
    assertTrue(result);
}