我如何模拟java.net.URL或以编程方式设置spring bean来接受构造函数参数?

时间:2014-08-14 12:52:48

标签: java spring unit-testing mocking

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

我在哪里
由于无法模拟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.
}

问题
我真正喜欢的是弄清楚如何让单元测试中“失败”的线路正常工作。这将对代码的其余部分产生最小的影响并且是理想的。我可以采取的另一种选择,但对我而言,感觉就像代码味道一样,是修改我的包装器以使用一个接受String的set方法并在内部创建一个新的URL对象。使用URL的单元测试代码的“正常”正确方法是什么?另外,如何在注入期间模拟接受构造函数参数的单元测试的上下文?

编辑1


上下文提供程序

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

2 个答案:

答案 0 :(得分:1)

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

在您的情况下,您将创建一个真正的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创建间谍,然后将该对象注册为该类的bean。

<强>更新

在我看来

url = (UrlWrapper) ContextProvider.getContext().getBean("url", requestUrl);

是对测试代码的限制。像这样的代码意味着需要以特定的方式(静态工厂方法)创建bean,以便能够按照测试期望创建。我建议你用一个已经包含所需行为的bean填充Spring上下文,然后只使用不带参数的getBean

答案 1 :(得分:1)

我终于想出了如何在运行时正确地模拟bean,这需要构造函数参数。这里的复杂性在于bean的范围是原型,因此我必须在弹簧内部的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>

以下是提供商。

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