如何模拟AngleSharp的HttpRequester在单元测试中提供静态内容?

问题描述 投票:0回答:1

我正在开发一个应用程序,其中包含一个用于收集数据的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方法执行此操作,但是我很难看到如何理智地执行此操作。

对于替代方法的其他建议也是受欢迎的 - 由于我的经验不足,我可能试图给不需要皮肤的猫皮肤。

moq xunit.net anglesharp
1个回答
0
投票

呜呜呜呜呜呜呜呜呜呜呜呜呜老实说,当钢锯可能在这里做得很好的时候,我可能还在使用电锯,所以我仍然欢迎对我的“解决方案”提出建设性的反馈。

为了让AngleSharp“做它的事情”并返回我保存到磁盘的文件的静态IDocument,我不得不做一些事情(其中一些是我在写这些问题时开始的,其他我想通了艰难的方式):

  1. 在DI可以注入的服务中包含对AngleSharp的调用(我可以模拟)
  2. 使用服务上的调用取一个IRequester参数,通常只需传递null并在null时使用new DefaultHttpRequester()
  3. 创建我自己的IRequester实现,但制作RequestAsyncSupportsProtocol virtual,以便Moq可以做到这一点。最简单的方法(在VS代码中)是右键单击与假类名相同的行上的接口,并让IDE自动生成实现。每个方法都会引发异常,但它会履行合同。
  4. 在调用被测单元之前进行两阶段舞蹈: 创建AngleSharp包装类的实例(真实实现,而不是接口或模拟),然后创建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);
}
© www.soinside.com 2019 - 2024. All rights reserved.