java 模拟/测试 HTTP 获取请求
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30791618/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mocking/Testing HTTP Get Request
提问by Kyle Bridenstine
I'm trying to write unit tests for my program and use mock data. I'm a little confused on how to intercept an HTTP Get request to a URL.
我正在尝试为我的程序编写单元测试并使用模拟数据。我对如何拦截对 URL 的 HTTP Get 请求感到有些困惑。
My program calls a URL to our API and it is returned a simple XML file. I would like the test to instead of getting the XML file from the API online to receive a predetermined XML file from me so that I can compare the output to the expected output and determine if everything is working correctly.
我的程序调用我们 API 的 URL,它返回一个简单的 XML 文件。我希望测试不是从在线 API 获取 XML 文件,而是从我那里接收预定的 XML 文件,以便我可以将输出与预期输出进行比较并确定一切是否正常工作。
I was pointed to Mockito and have been seeing many different examples such as this SO post, How to use mockito for testing a REST service?but it's not becoming clear to me how to set it all up and how to mock the data (i.e., return my own xml file whenever the call to the URL is made).
我被指向 Mockito 并且已经看到许多不同的示例,例如这篇 SO 帖子,如何使用 mockito 来测试 REST 服务?但我不清楚如何设置它以及如何模拟数据(即,每当调用 URL 时都返回我自己的 xml 文件)。
The only thing I can think of is having another program made that's running locally on Tomcat and in my test pass a special URL that calls the locally running program on Tomcat and then return the xml file that I want to test with. But that just seems like overkill and I don't think that would be acceptable. Could someone please point me in the right direction.
我唯一能想到的是让另一个程序在 Tomcat 上本地运行,并在我的测试中传递一个特殊的 URL,该 URL 调用在 Tomcat 上本地运行的程序,然后返回我想要测试的 xml 文件。但这似乎有点矫枉过正,我认为这是不可接受的。有人可以指出我正确的方向。
private static InputStream getContent(String uri) {
HttpURLConnection connection = null;
try {
URL url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
return connection.getInputStream();
} catch (MalformedURLException e) {
LOGGER.error("internal error", e);
} catch (IOException e) {
LOGGER.error("internal error", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
I am using Spring Boot and other parts of the Spring Framework if that helps.
如果有帮助,我正在使用 Spring Boot 和 Spring Framework 的其他部分。
采纳答案by Michael Anderson
Part of the problem is that you're not breaking things down into interfaces. You need to wrap getContent
into an interface and provide a concrete class implementing the interface. This concrete class will then
need to be passed into any class that uses the original getContent
. (This is essentially dependency inversion.) Your code will end up looking something like this.
部分问题在于您没有将事物分解为接口。您需要包装getContent
到一个接口中并提供一个实现该接口的具体类。然后需要将此具体类传递到任何使用原始getContent
. (这本质上是依赖倒置。)您的代码最终将看起来像这样。
public interface IUrlStreamSource {
InputStream getContent(String uri)
}
public class SimpleUrlStreamSource implements IUrlStreamSource {
protected final Logger LOGGER;
public SimpleUrlStreamSource(Logger LOGGER) {
this.LOGGER = LOGGER;
}
// pulled out to allow test classes to provide
// a version that returns mock objects
protected URL stringToUrl(String uri) throws MalformedURLException {
return new URL(uri);
}
public InputStream getContent(String uri) {
HttpURLConnection connection = null;
try {
Url url = stringToUrl(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
return connection.getInputStream();
} catch (MalformedURLException e) {
LOGGER.error("internal error", e);
} catch (IOException e) {
LOGGER.error("internal error", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
}
Now code that was using the static getContent
should go through a IUrlStreamSource
instances getContent()
. You then provide to the object that you want to test a mocked IUrlStreamSource
rather than a SimpleUrlStreamSource
.
现在使用静态的代码getContent
应该通过一个IUrlStreamSource
实例getContent()
。然后,您向要测试的对象提供模拟IUrlStreamSource
而不是SimpleUrlStreamSource
.
If you want to test SimpleUrlStreamSource
(but there's not much to test), then you can create a derived class that provides an implementation of stringToUrl
that returns a mock (or throws an exception).
如果您想测试SimpleUrlStreamSource
(但没有太多要测试的),那么您可以创建一个派生类,该类提供stringToUrl
返回模拟(或抛出异常)的实现。
回答by Raniz
The other answers in here advise you to refactor your code to using a sort of provider which you can replace during your tests - which is the better approach.
此处的其他答案建议您重构代码以使用一种可以在测试期间替换的提供程序 - 这是更好的方法。
If that isn't a possibility for whatever reason you can install a custom URLStreamHandlerFactory
that intercepts the URLs you want to "mock" and falls back to the standard implementation for URLs that shouldn't be intercepted.
如果无论出于何种原因都不可能,您可以安装一个自定义程序URLStreamHandlerFactory
来拦截您想要“模拟”的 URL,并回退到不应拦截的 URL 的标准实现。
Note that this is irreversible, so you can't remove the InterceptingUrlStreamHandlerFactory
once it's installed - the only way to get rid of it is to restart the JVM. You could implement a flag in it to disable it and return null
for all lookups - which would produce the same results.
请注意,这是不可逆的,因此您无法在InterceptingUrlStreamHandlerFactory
安装后删除它 - 摆脱它的唯一方法是重新启动 JVM。您可以在其中实现一个标志以禁用它并返回null
所有查找 - 这将产生相同的结果。
URLInterceptionDemo.java:
URLInterceptionDemo.java:
public class URLInterceptionDemo {
private static final String INTERCEPT_HOST = "dummy-host.com";
public static void main(String[] args) throws IOException {
// Install our own stream handler factory
URL.setURLStreamHandlerFactory(new InterceptingUrlStreamHandlerFactory());
// Fetch an intercepted URL
printUrlContents(new URL("http://dummy-host.com/message.txt"));
// Fetch another URL that shouldn't be intercepted
printUrlContents(new URL("http://httpbin.org/user-agent"));
}
private static void printUrlContents(URL url) throws IOException {
try(InputStream stream = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
private static class InterceptingUrlStreamHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if("http".equalsIgnoreCase(protocol)) {
// Intercept HTTP requests
return new InterceptingHttpUrlStreamHandler();
}
return null;
}
}
private static class InterceptingHttpUrlStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(final URL u) throws IOException {
if(INTERCEPT_HOST.equals(u.getHost())) {
// This URL should be intercepted, return the file from the classpath
return URLInterceptionDemo.class.getResource(u.getHost() + "/" + u.getPath()).openConnection();
}
// Fall back to the default handler, by passing the default handler here we won't end up
// in the factory again - which would trigger infinite recursion
return new URL(null, u.toString(), new sun.net.www.protocol.http.Handler()).openConnection();
}
}
}
dummy-host.com/message.txt:
dummy-host.com/message.txt:
Hello World!
When run, this app will output:
运行时,此应用程序将输出:
Hello World!
{
"user-agent": "Java/1.8.0_45"
}
It's pretty easy to change the criteria of how you decide which URLs to intercept and what you return instead.
更改决定拦截哪些 URL 以及返回哪些 URL 的标准非常容易。
回答by NamshubWriter
The answer depends on what you are testing.
答案取决于您要测试的内容。
If you need to test the processing of the InputStream
如果需要测试InputStream的处理
If getContent()
is called by some code that processes the data returned by the InputStream
, and you want to test how the processing code handles specific sets of input, then you need to create a seam to enable testing. I would simply move getContent()
into a new class, and inject that class into the class that does the processing:
如果getContent()
由处理由 返回的数据的某些代码调用InputStream
,并且您想测试处理代码如何处理特定的输入集,那么您需要创建一个接缝来启用测试。我会简单地getContent()
进入一个新类,并将该类注入到进行处理的类中:
public interface ContentSource {
InputStream getContent(String uri);
}
You could create a HttpContentSource
that uses URL.openConnection()
(or, better yet, the Apache HttpClientcode).
您可以创建一个HttpContentSource
使用URL.openConnection()
(或者,更好的是,Apache HttpClientcode)。
Then you would inject the ContentSource
into the processor:
然后你将注入ContentSource
处理器:
public class Processor {
private final ContentSource contentSource;
@Inject
public Processor(ContentSource contentSource) {
this.contentSource = contentSource;
}
...
}
The code in Processor
could be tested with a mock ContentSource
.
中的代码Processor
可以使用 mock 进行测试ContentSource
。
If you need to test the fetching of the content
如果需要测试内容的获取
If you want to make sure that getContent()
works, you could create a test that starts a lightweight in-memory HTTP server that serves the expected content, and have getContent()
talk to that server. That does seem overkill.
如果你想确保它getContent()
有效,你可以创建一个测试,启动一个轻量级的内存 HTTP 服务器,该服务器提供预期的内容,并getContent()
与该服务器进行对话。这似乎有点矫枉过正。
If you need to test a large subset of the system with fake data
如果您需要使用假数据测试系统的大部分子集
If you want to make sure things work end to end, write an end to end system test. Since you indicated you use Spring, you can use Spring to wire together parts of the system (or to wire the entire system, but with different properties). You have two choices
如果您想确保端到端的工作,请编写端到端的系统测试。由于您表示使用 Spring,因此您可以使用 Spring 将系统的各个部分连接在一起(或连接整个系统,但具有不同的属性)。你有两个选择
Have the system test start a local HTTP server, and when you have your test create your system, configure it to talk to that server. See the answers to this questionfor ways to start the HTTP server.
Configure spring to use a fake implementation of
ContentSource
. This gets you slightly less confidence that everything works end-to-end, but it will be faster and less flaky.
让系统测试启动一个本地 HTTP 服务器,当您让测试创建您的系统时,将其配置为与该服务器通信。有关启动 HTTP 服务器的方法,请参阅此问题的答案。
配置 spring 以使用
ContentSource
. 这让您对一切端到端运行的信心略有下降,但它会更快,更少不稳定。