背景
我正在使用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的单元测试代码的“正常”正确方法是什么?另外,如何在注入期间模拟接受构造函数参数的单元测试的上下文?
上下文提供程序
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 */
}
答案 0 :(得分:1)
我建议你看看Mockito的Spy
。 Here是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);
}