繁体   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