如何避免在 Java 中测试静态方法?
How to avoid testing static method in Java?
我想测试连接到 URL 的 class 以解析 html 文件(我正在使用 Jsoup)。问题是我不知道如何测试这个。我知道 PowerMockito 允许这样做,但如果可能的话,我宁愿通过重构代码并只测试重要部分来避免它。
这是我要进行单元测试的代码片段:
@Service
public class EurLexHtmlToHtmlService extends BaseHtmlToHtml {
private static final String eurlex_URL = "https://eur-lex.europa.eu/";
@Override
public InputStream urlToHtml(String url, boolean hasOnlyOneSheet, boolean hasBorders) throws IOException {
Document document = getDocument(url);
Element content = document.body();
Element cssLink = document.select("link").last();
String cssHref = cssLink.attr("href").replace("./../../../../", "");
//Method of BaseHtmlToHtml
addStyle(url, content, cssHref);
// Method of BaseHtmlToHtml
return toInputStream(content);
}
}
public abstract class BaseHtmlToHtml implements HtmlToHtmlService {
@Autowired
HtmlLayout htmlLayout;
protected ByteArrayInputStream toInputStream(Element content) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(content.outerHtml().getBytes());
outputStream.close();
return new ByteArrayInputStream(outputStream.toByteArray());
}
protected void addStyle(String url, Element content, String cssHref) throws IOException {
Document cssDoc = getDocument(url + cssHref);
Elements cssElements = cssDoc.getAllElements();
content.append(htmlLayout.getOpenStyleTag() + cssElements.outerHtml() + htmlLayout.getCloseStyleTag());
}
protected Document getDocument(String url) throws IOException {
return Jsoup.connect(url).get();
}
}
问题是我不知道如何解耦我的方法以便能够在不必调用 Jsoup.connect(url).get
的情况下进行测试
我的方法是“注入”一些returns核心对象:
而不是做:
protected Document getDocument(String url) throws IOException {
return Jsoup.connect(url).get();
}
你可以有一个静态字段:
private final Function<String, Document> documentReader; // fix the return type (Document)
还有两个构造函数:
BaseHtmlToHtml(Function<String, Document> documentReader) {
this.documentReader = documentReader;
}
BaseHtmlToHtml() {
this(Jsoup::connect);
}
protected Document getDocument(String url) throws IOException {
return documentReader.apply(url);
}
然后在你的测试中使用第一个构造函数,或者添加一个setter并更改默认值。
您也可以为此创建一个特定的 bean 并将其注入:在这种情况下,您只需要一个构造函数并确保注入 Jsoup::connect
。
这是一种无需模拟静态方法的方法 - 但您仍然需要模拟其余方法(例如:读取 url 并将其转换为 Document
)。
根据评论,这是一个带有 Spring bean 的示例:
声明一个完成工作的 bean:
@FunctionalInterface
interface DocumentResolver {
Document resolve(String url) throws IOException;
}
并且在您的生产代码中,声明一个使用 Jsoup 的 bean:
@Bean
public DocumentResolver documentResolver() {
return url -> Jsoup.connect(url).get();
}
让您的消费者使用这个 bean:
private final DocumentResolver resolver;
BaseHtmlToHtml(DocumentResolver resolver) {
this.resolver = resolver;
}
protected Document getDocument(String url) throws IOException {
return resolver.resolve(url);
}
在您的测试中,当您需要模拟行为时:
在您的测试中不使用 Spring 注入:在您的 JUnit 5 + AssertJ 测试中:
@Test
void get_the_doc() {
DocumentResolver throwingResolver = url -> {
throw new IOException("fail!");
};
BaseHtmlToHtml html = new BaseHtmlToHtml(throwingResolver);
assertThatIOException()
.isThrownBy(() -> html.urlToHtml("foobar", false, false))
.withMessage("fail!")
;
}
当然,你必须修复任何你需要修复的东西(例如:类型)。
这个例子没有使用Spring注入:如果你想模拟DocumentResolver
,我认为你不能求助于注入,或者如果你这样做,你将不得不重新设置每次模拟,除非 Spring 测试为每次测试执行生成一个新容器:
@TestConfiguration
static class MyTestConfiguration {
@Bean
public DocumentResolver documentResolver() {
return mock(DocumentResolver.class);
}
}
然后使用 JUnit 5 参数解析器:
@Test
void get_the_doc(DocumentResolver resolver, BaseHtmlToHtml html) {
doThrow(new IOException("fail!")).when(resolver).resolve(anyString());
assertThatIOException()
.isThrownBy(() -> html.urlToHtml("foobar", false, false))
.withMessage("fail!")
;
}
请注意,我对此一无所知,您将不得不尝试。
我想测试连接到 URL 的 class 以解析 html 文件(我正在使用 Jsoup)。问题是我不知道如何测试这个。我知道 PowerMockito 允许这样做,但如果可能的话,我宁愿通过重构代码并只测试重要部分来避免它。
这是我要进行单元测试的代码片段:
@Service
public class EurLexHtmlToHtmlService extends BaseHtmlToHtml {
private static final String eurlex_URL = "https://eur-lex.europa.eu/";
@Override
public InputStream urlToHtml(String url, boolean hasOnlyOneSheet, boolean hasBorders) throws IOException {
Document document = getDocument(url);
Element content = document.body();
Element cssLink = document.select("link").last();
String cssHref = cssLink.attr("href").replace("./../../../../", "");
//Method of BaseHtmlToHtml
addStyle(url, content, cssHref);
// Method of BaseHtmlToHtml
return toInputStream(content);
}
}
public abstract class BaseHtmlToHtml implements HtmlToHtmlService {
@Autowired
HtmlLayout htmlLayout;
protected ByteArrayInputStream toInputStream(Element content) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(content.outerHtml().getBytes());
outputStream.close();
return new ByteArrayInputStream(outputStream.toByteArray());
}
protected void addStyle(String url, Element content, String cssHref) throws IOException {
Document cssDoc = getDocument(url + cssHref);
Elements cssElements = cssDoc.getAllElements();
content.append(htmlLayout.getOpenStyleTag() + cssElements.outerHtml() + htmlLayout.getCloseStyleTag());
}
protected Document getDocument(String url) throws IOException {
return Jsoup.connect(url).get();
}
}
问题是我不知道如何解耦我的方法以便能够在不必调用 Jsoup.connect(url).get
我的方法是“注入”一些returns核心对象:
而不是做:
protected Document getDocument(String url) throws IOException {
return Jsoup.connect(url).get();
}
你可以有一个静态字段:
private final Function<String, Document> documentReader; // fix the return type (Document)
还有两个构造函数:
BaseHtmlToHtml(Function<String, Document> documentReader) {
this.documentReader = documentReader;
}
BaseHtmlToHtml() {
this(Jsoup::connect);
}
protected Document getDocument(String url) throws IOException {
return documentReader.apply(url);
}
然后在你的测试中使用第一个构造函数,或者添加一个setter并更改默认值。
您也可以为此创建一个特定的 bean 并将其注入:在这种情况下,您只需要一个构造函数并确保注入 Jsoup::connect
。
这是一种无需模拟静态方法的方法 - 但您仍然需要模拟其余方法(例如:读取 url 并将其转换为 Document
)。
根据评论,这是一个带有 Spring bean 的示例:
声明一个完成工作的 bean:
@FunctionalInterface
interface DocumentResolver {
Document resolve(String url) throws IOException;
}
并且在您的生产代码中,声明一个使用 Jsoup 的 bean:
@Bean
public DocumentResolver documentResolver() {
return url -> Jsoup.connect(url).get();
}
让您的消费者使用这个 bean:
private final DocumentResolver resolver;
BaseHtmlToHtml(DocumentResolver resolver) {
this.resolver = resolver;
}
protected Document getDocument(String url) throws IOException {
return resolver.resolve(url);
}
在您的测试中,当您需要模拟行为时:
在您的测试中不使用 Spring 注入:在您的 JUnit 5 + AssertJ 测试中:
@Test
void get_the_doc() {
DocumentResolver throwingResolver = url -> {
throw new IOException("fail!");
};
BaseHtmlToHtml html = new BaseHtmlToHtml(throwingResolver);
assertThatIOException()
.isThrownBy(() -> html.urlToHtml("foobar", false, false))
.withMessage("fail!")
;
}
当然,你必须修复任何你需要修复的东西(例如:类型)。
这个例子没有使用Spring注入:如果你想模拟DocumentResolver
,我认为你不能求助于注入,或者如果你这样做,你将不得不重新设置每次模拟,除非 Spring 测试为每次测试执行生成一个新容器:
@TestConfiguration
static class MyTestConfiguration {
@Bean
public DocumentResolver documentResolver() {
return mock(DocumentResolver.class);
}
}
然后使用 JUnit 5 参数解析器:
@Test
void get_the_doc(DocumentResolver resolver, BaseHtmlToHtml html) {
doThrow(new IOException("fail!")).when(resolver).resolve(anyString());
assertThatIOException()
.isThrownBy(() -> html.urlToHtml("foobar", false, false))
.withMessage("fail!")
;
}
请注意,我对此一无所知,您将不得不尝试。