簡體   English   中英

具有多個設置斷言的 Moq SetupSequence

[英]Moq SetupSequence with multiple setup assertions

我正在編寫一些測試,其中我正在測試的 class 取決於HttpClient 嘲笑我是 mocking 一個HttpMessageHandler並將其傳遞給HttpClient構造函數。

為此,我有一個基數 class:

public class HttpTestBase
{
    protected static readonly string BaseAddress = "https://test.com";

    protected readonly HttpClient _httpClient;
    protected readonly Mock<HttpMessageHandler> _httpMessageHandlerMock;

    public HttpTestBase()
    {
        _httpMessageHandlerMock = new Mock<HttpMessageHandler>();
        _httpClient = new HttpClient(_httpMessageHandlerMock.Object);
        _httpClient.BaseAddress = new Uri(BaseAddress);
    }

    protected void MockHttpResponse(HttpResponseMessage message, string expectedPath, HttpMethod expectedMethod)
    {
        _httpMessageHandlerMock
          .Protected()
          .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.Is<HttpRequestMessage>(request => AssertRequestParameters(expectedPath, expectedMethod, request)),
            ItExpr.IsAny<CancellationToken>())
          .ReturnsAsync(message);
    }

    private bool AssertRequestParameters(string expectedPath, HttpMethod expectedMethod, HttpRequestMessage request)
    {
       // Throw an exception if the method or path does not match what is expected.
    }
}

然后測試如下所示,在測試 class 中繼承了這個測試庫:

[Fact]
public async Task GetAvailableLicenseCount()
{
    // Arrange
    var licenses = new JsonObject
    {
        ["total_seats_consumed"] = 4500,
        ["total_seats_purchased"] = 5000
    };

    MockHttpResponse(
        new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(licenses.ToJsonString()) },
        expectedPath: "/enterprises/enterprise/consumed-licenses",
        expectedMethod: HttpMethod.Get
    );

    // Act
    var result = await sut.GetAvailableLicenseCount();

    // Assert
    result.Should().Be(500);
}

當一個方法只執行一次調用時,這很有效。 一些方法按順序執行多個調用。 我已經部分解決了這個問題,但我仍然希望在設置中有斷言(例如,當我調用AssertRequestParameters時)。

為了支持鏈中的多個調用,我改為在 base class 中執行此操作:

protected void AddHttpMockResponse(HttpResponseMessage message, string expectedPath, HttpMethod expectedMethod)
{
    _responseMocks.Enqueue(new HttpMock
    {
        Response = message,
        Assertion = new Assertion { ExpectedPath = expectedPath, ExpectedMethod = expectedMethod }
    });
}

protected void MockHttpResponses()
{
    _httpMessageHandlerMock
        .Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(() => _responseMocks.Dequeue().Response);
}

如您所見,我已經刪除了設置中的斷言,因為斷言似乎發生了沖突,並且我從模擬中得到了錯誤的返回值。

為了使用它,我重構了測試如下:

[Fact]
public async Task GetAvailableLicenseCount()
{
    // Arrange
    var licenses = new JsonObject
    {
        ["total_seats_consumed"] = 4500,
        ["total_seats_purchased"] = 5000
    };

    AddHttpMockResponse(
        new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(licenses.ToJsonString()) },
        expectedPath: "/enterprises/enterprise/consumed-licenses",
        expectedMethod: HttpMethod.Get
    );

    MockHttpResponses();

    // Act
    var result = await sut.GetAvailableLicenseCount();

    // Assert
    result.Should().Be(500);
}

這也適用於來自同一模擬的多個響應。

但是,我希望能夠模擬多個響應,其中響應由我在模擬Setup中使用的內容決定。 我想這樣做的原因是我無法在HttpMessageHandler上使用Verify ,因為我無法訪問該方法。

是否可以根據模擬設置中完成的操作獲得動態返回值?

如果我正確理解你的問題,你會想要這樣的東西:

我最近不得不做類似的事情,發現下面的文章非常有幫助。 檢查“使用 Moq 模擬 HttpMessageHandler”部分。 它向您展示了如何返回您創建的HttpResponseMessage object,這正是我認為您可能想要的。

https://code-maze.com/csharp-mock-httpclient-with-unit-tests/

class 上的字段

private readonly Mock<HttpMessageHandler> _httpMessageHandlerStub;

在您的設置中(構造函數或單獨的方法)

_httpMessageHandlerStub = new Mock<HttpMessageHandler>();

var httpClient = new HttpClient(_httpMessageHandlerStub.Object);

_sut = new SomeService(
    httpClient,
    anotherDependency
)

然后您可以在每個測試中配置以下內容

[Fact]
public async Task GetListAsync_Successfully_Uses_Api_Key()
{
    var unauthorisedResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
    {
        Content = new StringContent($"'{_apiKeyHeaderName}' header not found or API key is incorrect")
    };

    var successResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
    {
        Content = new StringContent(JsonConvert.SerializeObject(new List<SomeDto>()))
    };

    // Return 200 if api key header is present and the value is correct.
    _httpMessageHandlerStub.Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.Is<HttpRequestMessage>(x => x.Method == HttpMethod.Get && x.Headers.Any(h => h.Key == _apiKeyHeaderName && h.Value.FirstOrDefault() == _apiKeyHeaderValue)),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(successResponseMessage);

    // Return 401 is api key header is not present or api key value is incorrect.
    _httpMessageHandlerStub.Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.Is<HttpRequestMessage>(x => x.Method == HttpMethod.Get && !x.Headers.Any(h => h.Key == _apiKeyHeaderName && h.Value.FirstOrDefault() == _apiKeyHeaderValue)),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(unauthorisedResponseMessage);

    var result = _sut.GetListAsync();

    // Assert stuff...
}

希望這會有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM