如何让单元测试调用HtmlWeb子类的Mock Load方法?
解决方案:无需修改业务代码的Mock方案
你的核心问题是new关键字隐藏基类方法不具备多态性,当被测类以HtmlWeb类型接收实例时,调用的仍是基类的Load方法。以下是两种无需修改业务代码的可行方案:
方案1:利用HtmlWeb的protected虚方法(推荐)
HtmlWeb的Load方法内部依赖protected virtual的GetResponse方法,我们可以通过Moq的Protected()扩展Mock这个方法,从而控制返回的HTML内容,完全绕过非虚的Load方法限制。
测试代码示例:
// 构建Mock的HtmlWeb实例 var mockHtmlWeb = new Mock<HtmlWeb>(); // 预设要返回的测试HTML内容 var testHtml = "<html><body><img src='img1.png'><img src='img2.png'></body></html>"; // 伪造WebResponse,返回预设的HTML流 var fakeResponse = new Mock<WebResponse>(); fakeResponse.Setup(r => r.GetResponseStream()) .Returns(new MemoryStream(Encoding.UTF8.GetBytes(testHtml))); // 设置HtmlWeb的protected虚方法GetResponse,返回伪造响应 mockHtmlWeb.Protected() .Setup<WebResponse>("GetResponse", ItExpr.IsAny<WebRequest>()) .Returns(fakeResponse.Object); // 注入Mock实例到被测服务 var downloadService = new AgilityPackHtmlDownloadService(mockHtmlWeb.Object); // 执行测试并断言结果 var imageUrls = downloadService.GetImageUrls("http://dummy.url"); Assert.Equal(2, imageUrls.Count); Assert.Contains("img1.png", imageUrls); Assert.Contains("img2.png", imageUrls);
方案2:自定义Fake子类(无需Moq扩展)
如果不想依赖Moq的Protected扩展,可以自定义一个HtmlWeb的子类,重写GetResponse方法返回伪造内容,直接将子类实例注入被测服务:
// 自定义Fake HtmlWeb子类 public class FakeHtmlWeb : HtmlWeb { private readonly string _testHtml; public FakeHtmlWeb(string testHtml) { _testHtml = testHtml; } protected override WebResponse GetResponse(WebRequest request) { var htmlStream = new MemoryStream(Encoding.UTF8.GetBytes(_testHtml)); // 通过反射设置WebResponse的私有流字段 var fakeResponse = new Mock<WebResponse>().Object; typeof(WebResponse).GetField("_responseStream", BindingFlags.Instance | BindingFlags.NonPublic) ?.SetValue(fakeResponse, htmlStream); return fakeResponse; } } // 测试代码 var testHtml = "<html><body><img src='test-img.jpg'></body></html>"; var fakeHtmlWeb = new FakeHtmlWeb(testHtml); var downloadService = new AgilityPackHtmlDownloadService(fakeHtmlWeb); var imageUrls = downloadService.GetImageUrls("http://dummy.url"); Assert.Single(imageUrls); Assert.Equal("test-img.jpg", imageUrls[0]);
原有方案失效原因
你用new声明的Load方法是隐藏基类方法,而非重写(override)。C#的多态机制只对override修饰的方法生效:当变量类型为基类HtmlWeb时,调用Load会直接执行基类方法,不会触发子类的new方法——只有当变量类型为子类HtmlWebEx时,才会调用子类的Load方法。
内容的提问来源于stack exchange,提问作者Howard
相关产品推荐
相关产品推荐

