我有以下课程,它使用 HTML Agility Pack 下载文件。 GetImageUrls 方法有一些逻辑,我想通过 Moq (https://github.com/moq/moq) 进行单元测试:
public class AgilityPackHtmlDownloadService : IHtmlDownloadService
{
private HtmlWeb _htmlWeb;
public AgilityPackHtmlDownloadService() : this(new HtmlWeb())
{
}
public AgilityPackHtmlDownloadService(HtmlWeb htmlWeb)
{
_htmlWeb = htmlWeb;
}
public IEnumerable<string> GetImageUrls(string pageUrl)
{
if (string.IsNullOrWhiteSpace(pageUrl))
{
throw new System.Exception("The Page URL cannot be blank");
}
var document = _htmlWeb.Load(pageUrl);
var result = new List<string>();
foreach (var element in document.DocumentNode.Descendants("img"))
{
var src = element.GetAttributeValue("src", null);
var dataSrc = element.GetAttributeValue("data-src", null);
var imageUrl = src ?? dataSrc;
if (!string.IsNullOrWhiteSpace(imageUrl))
{
result.Add(imageUrl);
}
}
return result;
}
}
HtmlWeb 类不基于接口,因此有两个构造函数的原因——我想通过第二个构造函数注入 HtmlWeb 的模拟实例。
需要mock HtmlWeb的Load方法,定义为“public”,但不是virtual,Moq无法处理
因此我创建了这个类:
public class HtmlWebEx : HtmlWeb
{
public virtual new HtmlDocument Load(string url)
{
return base.Load(url);
}
}
因为这个子类有一个名为 Load 的虚方法,它可以通过 Moq 像这样模拟:
var mockHtmlWeb = new Mock<HtmlWebEx>();
mockHtmlWeb.Setup(x => x.Load(It.IsAny<string>())).Returns(mockHtmlDocument);
IHtmlDownloadService htmlDownloadService = new AgilityPackHtmlDownloadService(mockHtmlWeb.Object);
因为被测类在构造函数中需要一个 HtmlWeb 参数,而不是 HtmlWebEx,所以 _htmlWeb.Load 执行基类的 Load 方法,绕过我的 Mock 实现。
(如果我使用标准的虚拟/override 方法,将调用子类的 Load 方法,但我没有覆盖,只是隐藏)。
如何让代码在运行单元测试时调用子类的模拟 Load 方法?
我为构造函数尝试了以下代码,即更改参数的类型,这确实有效,但现在我修改了代码只是为了使单元测试工作,这似乎不正确:
public AgilityPackHtmlDownloadService() : this(new HtmlWebEx()) { //... }
public AgilityPackHtmlDownloadService(HtmlWebEx htmlWeb)
{
_htmlWeb = htmlWeb;
}
创建界面:
public interface IHtmlWeb
{
HtmlDocument Load(string url);
}
创建一个继承 HtmlWeb 类的类,并声明它实现了接口:
public class HtmlWebInterfaced : HtmlWeb, IHtmlWeb { }
注入接口/模拟的实现:
public class AgilityPackHtmlDownloadService : IHtmlDownloadService
{
private readonly IHtmlWeb _htmlWeb;
public AgilityPackHtmlDownloadService(IHtmlWeb htmlWeb)
{
_htmlWeb = htmlWeb;
}
// rest of class removed for brevity...
}
在使用中,对 Load 方法的调用将由基类上的方法处理。