我正在开发一个应用程序,其中包含一个用于收集数据的Web scraper,并希望验证特定服务(将来会扩展到多个服务)是否正在执行正确的业务逻辑,因为预期的HTML DOM树将返回给它。
我不是每次运行测试时都执行实际的HTTP请求,而是希望通过为测试提供静态文档并返回预定义的HTML文档来“模拟”它。我宁愿我的单元测试反映“给定这个HTML文档,验证输出是否正确商业”,这不需要包含AngleSharp的HTTP请求。
以下是我现在加载文档的方法,我将其放入“包装”服务中,然后通过依赖注入将其注入到我的服务中:
var angleSharpConfig = Configuration.Default.WithDefaultLoader();
var angleSharpContext = BrowsingContext.New(angleSharpConfig);
var document = await angleSharpContext.OpenAsync(url);
我看到有一个LoaderOptions
对象可以传递给WithDefaultLoader
,但它似乎没有提供模拟HTTP请求的方法。似乎可能有一种方法可以在默认配置对象上使用With
方法执行此操作,但是我很难看到如何理智地执行此操作。
对于替代方法的其他建议也是受欢迎的 - 由于我的经验不足,我可能试图给不需要皮肤的猫皮肤。
呜呜呜呜呜呜呜呜呜呜呜呜呜老实说,当钢锯可能在这里做得很好的时候,我可能还在使用电锯,所以我仍然欢迎对我的“解决方案”提出建设性的反馈。
为了让AngleSharp“做它的事情”并返回我保存到磁盘的文件的静态IDocument
,我不得不做一些事情(其中一些是我在写这些问题时开始的,其他我想通了艰难的方式):
IRequester
参数,通常只需传递null
并在null时使用new DefaultHttpRequester()
IRequester
实现,但制作RequestAsync
和SupportsProtocol
virtual
,以便Moq可以做到这一点。最简单的方法(在VS代码中)是右键单击与假类名相同的行上的接口,并让IDE自动生成实现。每个方法都会引发异常,但它会履行合同。IRequester
的最小模拟,模拟地址,标题和内容(从磁盘读取HTML文件)并将其传递给包装器方法,取回磁盘上HTML文件的完全合法的IDocument
。
创建一个AngleSharp包装类的模拟并模拟该方法,告诉它返回上一步中的IDocument
,然后为您测试的单元提供此方法。GAH。这是疯了,我现在明白为什么这个问题是谷歌的第一个结果:)
无论如何,对于具体细节,这里是我提出的解决这个问题的代码的粗略草图:
测试:
[Fact]
public async void ItShouldDoSomething() {
using(var autoMock = AutoMock.GetLoose()) {
var mockedWrapper = autoMock.Mock<IAngleSharpWrapper>();
var fakeDocument = await GetFakeDocument();
mockedWrapper.Setup(x => x.OpenUrlAsync(It.IsAny<IRequester>())).ReturnsAsync(fakeDocument);
autoMock.Provide(mockedWrapper);
var sut = autoMock.Create<ClassYouAreTesting>();
var data = await sut.LoadData();
//Perform your assertions here
}
}
相关的存根和嘲弄方法:
private async Task<IDocument> GetFakeDocument() {
var angleSharpWrapper = new AngleSharpWrapper();
var requesterMock = GetFakeRequesterMock();
return await angleSharpWrapper.OpenUrlAsync("http://askjdkaj", requesterMock.Object);
}
private Mock<FakeRequester> GetFakeRequesterMock() {
var mockResponse = new Mock<IResponse>();
mockResponse.Setup(x => x.Address).Returns(new Url("fakeaddress"));
mockResponse.Setup(x => x.Headers).Returns(new Dictionary<string, string>());
mockResponse.Setup(_ => _.Content).Returns(LoadFakeDocumentFromFile());
var mockFakeRequester = new Mock<FakeRequester>();
mockFakeRequester.Setup(_ => _.RequestAsync(It.IsAny<Request>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockResponse.Object);
mockFakeRequester.Setup(x => x.SupportsProtocol(It.IsAny<string>())).Returns(true);
return mockFakeRequester;
}
private MemoryStream LoadFakeDocumentFromFile() {
//Note: path below is relative to your output directory, so bin/Debug/etc.
using (FileStream fileStream = File.OpenRead(Path.Combine(Directory.GetCurrentDirectory(), "Path/To/Your/LocalFile.html")))
{
MemoryStream memoryStream = new MemoryStream();
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int)fileStream.Length);
return memoryStream;
}
}
*编辑:想想我应该在AngleSharp周围包含包装器方法,这有点重要:
public async Task<IDocument> OpenUrlAsync(string url, IRequester defaultRequester)
{
if (defaultRequester == null) {
defaultRequester = new DefaultHttpRequester();
}
var angleSharpConfig = Configuration.Default.WithDefaultLoader().With(defaultRequester);
var angleSharpContext = BrowsingContext.New(angleSharpConfig);
return await angleSharpContext.OpenAsync(url);
}