Подтвердить что ты не робот

Unit Test Windows.Web.Http HttpClient с посмеянным IHttpFilter и IHttpContent, MockedHttpFilter выдает System.InvalidCastException

У меня есть класс, который зависит от HttpClient от Windows.Web.Http(Windows 10 UAP App). Я хочу unit test, и поэтому мне нужно "высмеять" HttpClient, чтобы настроить то, что должен вернуть Get-Call. Я начал с "простого" unit test с HttpClient, используя рукописный IHttpFilter и IHttpContent. Он работает не так, как ожидалось, и я получаю InvalidCastException в тесте-проводнике.

unit test выглядит следующим образом:

    [TestMethod]
    public async Task TestMockedHttpFilter()
    {
        MockedHttpContent mockedContent = new MockedHttpContent("Content from MockedHttpContent");
        MockedHttpFilter mockedHttpFilter = new MockedHttpFilter(HttpStatusCode.Ok, mockedContent);

        HttpClient httpClient = new HttpClient(mockedHttpFilter);
        var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false);
        // Test stops here, throwing System.InvalidCastException: Specified cast is not valid

        // Code not reached...
        var result = await resultContentTask.Content.ReadAsStringAsync();
        Assert.AreEqual("Content from MockedHttpContent", result);
    }

Я реализовал IHttpFilter в MockedHttpFilter:

public class MockedHttpFilter : IHttpFilter
{
    private HttpStatusCode _statusCode;
    private IHttpContent _content;

    public MockedHttpFilter(HttpStatusCode statusCode, IHttpContent content)
    {
        _statusCode = statusCode;
        _content = content;
    }

    public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
    {
        return AsyncInfo.Run<HttpResponseMessage, HttpProgress>((token, progress) =>
        Task.Run<HttpResponseMessage>(()=>
        {
            HttpResponseMessage response = new HttpResponseMessage(_statusCode);
            response.Content = _content;
            return response; // Exception thrown after return, but not catched by code/debugger...
        }));
    }
}

Я реализовал IHttpContent в MockedHttpContent:

public class MockedHttpContent : IHttpContent
{
    private string _contentToReturn;

    public MockedHttpContent(string contentToReturn)
    {
        _contentToReturn = contentToReturn;
    }

    public HttpContentHeaderCollection Headers
    {
        get
        {
            return new HttpContentHeaderCollection();
        }
    }

    public IAsyncOperationWithProgress<string, ulong> ReadAsStringAsync()
    {
        return AsyncInfo.Run<string, ulong>((token, progress) => Task.Run<string>(() =>
        {
            return _contentToReturn;
        }));
    }
}

Ошибка в представлении результата Test-Explorer:

Test Name:  TestMockedHttpFilter
Test FullName:  xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter
Test Source:    xxx.UnitTests\xxxHttpClientUnitTests.cs : line 22
Test Outcome:   Failed
Test Duration:  0:00:00.1990313

Result StackTrace:  
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at xxx.UnitTests.xxxHttpClientUnitTests.<TestMockedHttpFilter>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message: Test method xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter threw exception: 
System.InvalidCastException: Specified cast is not valid.

Во-первых, не уверен, почему исключение выбрано/что я делаю неправильно. Может быть, кто-то может указать мне в правильном направлении или дать подсказку, что проверить/проверить дальше?

Во-вторых, есть ли лучший способ для unit test кода с зависимостью HttpClient (Windows 10 UAP)?

4b9b3361

Ответ 1

Вы unit test не имеет смысла, вы действительно не тестируете свой класс, но вместо этого вы пытаетесь протестировать класс HttpClient. Я собираюсь принять несколько вещей, так как у нас нет метода, который тестируется в вашем вопросе; Я предполагаю, что вы делаете что-то вроде следующего:

public async Task<MyCustomClass> MethodUnderTest()
{
    // ...

    using (var client = new HttpClient(...))
    {
        // ...

        var json = response.Content.ReadAsStringAsync();
        return JsonConvert.Deserialize<MyCustomClass>(json);
    }
}

Если это так, то вам нужно понять, что ваш класс не принимает определенные зависимости. Вы можете либо изменить класс, чтобы каждая внешняя зависимость была введена, но это может быть небольшим излишеством... действительно ли вы хотите, чтобы кто-то, кто потребляет и создает экземпляр вашего класса, должен предоставить вам HttpClient? Итак, лучшей альтернативой является использование насмешливой структуры, которая может издеваться над зависимостью конкретного класса; большинство издевательских фреймворков могут обрабатывать смешные интерфейсы (это легко), но очень немногие могут обрабатывать насмешливые классы.

Я бы предложил использовать фреймворк Microsoft Fakes. Microsoft Fakes поддерживает Shims (Изоляция) и Stubs (Mocks). Изоляция - это то, что вам нужно, чтобы контролировать вызовы участников конкретных классов.

Итак, учитывая мой пример, какие члены должны контролироваться?

Я не думаю, что вам нужно изменить поведение члена JsonConvert.Deserialize<T>(), но вы могли бы, если бы захотели. Сначала платформа Microsoft Fakes немного сложна, но как только вы начнете ее использовать и заработаете, она станет проще в использовании. Кроме того, вы можете использовать другие фреймворки, поддерживающие Isolation:

  • JustMock от Telerik
  • Изолятор TypeMock

Возможно, другие существуют, я не знаком с ними.

Ответ 2

Кажется, вы не используете перегрузку AsTask, которая соответствует возвращаемому значению httpClient.SendRequestAsync(...)

попробуйте изменить этот

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false);

к этому

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask<IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress>>().ConfigureAwait(false);

Ответ 3

Самое простое решение - использовать эту библиотеку для насмешки HttpClient

https://github.com/richardszalay/mockhttp

PM> Install-Package RichardSzalay.MockHttp

Я также тестировал его с помощью прилагаемого кода

       //Arrange
        var mockHttp = new MockHttpMessageHandler();
        mockHttp.When("http://dontcare.ch")
            .Respond("application/txt", "Content from MockedHttpContent"); // Respond with content
        var client = mockHttp.ToHttpClient();

        //Act
        var response = await client.GetAsync("http://dontcare.ch");
        var result = await response.Content.ReadAsStringAsync();

        //Assert
        Assert.AreEqual("Content from MockedHttpContent", result);