Как издеваться над HttpRequester AngleSharp, чтобы обеспечить статический контент в модульных тестах? - PullRequest
0 голосов
/ 24 февраля 2019

Я работаю над приложением, которое включает в себя веб-скребок для сбора данных и хотел бы проверить, что конкретный сервис (который в будущем вырастет до нескольких сервисов) выполняет правильную бизнес-логику, учитывая, что ожидаемое дерево HTML DOMвернулся к нему.

Вместо того, чтобы выполнять реальные HTTP-запросы при каждом запуске теста, я бы предпочел «смоделировать» это, предоставив статический документ для теста и вернув предварительно определенный HTML-документ.Вместо этого я бы предпочел, чтобы в моем модульном тесте отображалось «Учитывая этот HTML-документ, убедитесь, что вывод является корректным с точки зрения бизнеса», и в него не нужно включать HTTP-запрос AngleSharp.Я поместил в службу-оболочку, которую затем могу внедрить в службу через внедрение зависимостей:

var angleSharpConfig = Configuration.Default.WithDefaultLoader();
var angleSharpContext = BrowsingContext.New(angleSharpConfig);
var document = await angleSharpContext.OpenAsync(url);

Я вижу, что существует объект LoaderOptions, который можно передать WithDefaultLoader, ноПохоже, что он не обеспечивает способ имитации HTTP-запроса.Возможно, существует способ сделать это с помощью метода With для объекта конфигурации по умолчанию, однако я изо всех сил пытаюсь понять, как разумно это сделать.

Другие предложения для альтернативных подходов приветствуются, так какхорошо - из-за моей неопытности я, возможно, пытаюсь снять кожу с кошки, которую вообще не нужно снимать с кожи.

1 Ответ

0 голосов
/ 25 февраля 2019

О-о-о, парень, это был один изумительный.И, честно говоря, я, вероятно, все еще использую бензопилу, когда ножовка, возможно, справилась здесь достаточно хорошо, поэтому я все же буду рад конструктивному отзыву о моем «решении» здесь.

Для того, чтобыAngleSharp, чтобы «сделать свое дело» и вернуть мне статический IDocument файла, который я сохранил на диск, мне пришлось сделать несколько вещей (некоторые из которых я начал, когда писал эти вопросы, другие я понял сложныйway):

  1. Обернуть вызов AngleSharp в сервис, который DI может затем внедрить (и я могу фальсифицировать)
  2. Заставить указанный вызов в сервисе принять параметр IRequester, обычнопросто передайте null и используйте new DefaultHttpRequester(), когда ноль
  3. Создайте мою собственную реализацию IRequester, но сделайте RequestAsync и SupportsProtocol virtual, чтобы Moq мог делать свое дело.Самый простой способ сделать это (в VS Code) - щелкнуть правой кнопкой мыши интерфейс в той же строке, что и ваше поддельное имя класса, и заставить IDE автоматически сгенерировать реализацию.Каждый метод генерирует исключение, но он выполняет контракт.
  4. Выполните двухэтапный танец перед вызовом тестируемого модуля:
    • Создайте экземпляр вашего класса-оболочки AngleSharp (истинная реализация,а не интерфейс или макет), затем создайте минимальный макет IRequester, макет Адреса, Заголовки и Контент (чтение HTML-файла с диска) и передайте его методу-обертке, возвращая полноценный закон IDocument HTML-файла на диске.
    • Создайте макет вашего класса-обертки AngleSharp и смоделируйте метод, сказав ему вернуть IDocument из предыдущего шага, а затем предоставьте его для тестируемого модуля.

GAH.Это безумие, и теперь я понимаю, почему этот вопрос является первым результатом в Google:)

В любом случае, для подробностей, вот примерный набросок кода, который я придумал, чтобы решить эту проблему:

Тест:

[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);
}
...