Func<HttpRequestMessage> 相关的单元测试模拟问题

Func<HttpRequestMessage> related Mocking Questions for Unit Tests

我在我的 XUnit 测试中使用了 Moq。在依赖方法中,它包含一个 Func<HttpResponseMessage> 参数。这是我写的单元测试:

      [Fact]
    public async Task Test()
    {
        //Arrange
        var content = new StringContent(TestData.GetResponse().ToString(), Encoding.UTF8, "application/json");
        var httpResponse = new HttpResponseMessage()
        {
            StatusCode = HttpStatusCode.OK,
            Content = content
        };
        _mockRetryHttpRequest.Setup(x => x.ExecuteAsync(It.IsAny<Func<HttpRequestMessage>>(), It.IsAny<HttpClient>(), It.IsAny<int>()))
                           .ReturnsAsync(httpResponse);
        var libraryService = new LibraryService(_mockRetryHttpRequest.Object);

        //Act
        var response = await libraryService.GetResponseForSearch(new SearchRequest(), null);

        //Assert
        response.Should().NotBeNull();
    }

这是我需要测试的实际方法

public class LibraryService : ILibraryService
{
    private IRetryHttpRequest _retryHttpRequest;
    public LibraryService(IRetryHttpRequest retryHttpRequest)
    {
        _retryHttpRequest = retryHttpRequest;
    }


    public async Task<ResponseModel> GetResponseForSearch(SearchRequest searchRequest, HttpClient client)
    {
        //send request and retry if failed
        ResponseModel result = new ResponseModel();
        HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => new HttpRequestMessage(), client, 3);

        //process response
        if (httpResponseMessage != null)
        {
            string response = await httpResponseMessage.Content.ReadAsStringAsync();
            result = JsonConvert.DeserializeObject<ResponseModel>(response);
        }
        return result;
    }
}

public class RetryHttpRequest : IRetryHttpRequest
{
    public async Task<HttpResponseMessage> ExecuteAsync(Func<HttpRequestMessage> requestMessage, HttpClient client, int maxTryValue)
    {
        var content = new StringContent("From Execute Async", Encoding.UTF8, "application/json");
        var httpResponse = new HttpResponseMessage()
        {
            StatusCode = HttpStatusCode.OK,
            Content = content
        };
        return httpResponse;
    }
}

当我单步执行代码时,对于下面这行代码,httpResponseMessage 变量返回 null,尽管我已经在单元测试中用 200 响应模拟了它。

HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => new HttpRequestMessage(), client, 3);

虽然问题似乎缺乏完整的解释,但以下内容用于演示目的,以展示如何使用最小起订量测试目标方法。

假设如下

public interface IRetryHttpRequest {
    Task<HttpResponseMessage> ExecuteAsync(Func<HttpRequestMessage> requestMessage, HttpClient client, int maxTryValue);
}

public class LibraryService : ILibraryService {
    private IRetryHttpRequest _retryHttpRequest;
    public LibraryService(IRetryHttpRequest retryHttpRequest) {
        _retryHttpRequest = retryHttpRequest;
    }

    public async Task<ResponseModel> GetResponseForSearch(SearchRequest searchRequest, HttpClient client) {
        //send request and retry if failed
        ResponseModel result = new ResponseModel();
        HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => new HttpRequestMessage(), client, 3);

        //process response
        if (httpResponseMessage != null) {
            string response = await httpResponseMessage.Content.ReadAsStringAsync();
            result = JsonConvert.DeserializeObject<ResponseModel>(response);
        }
        return result;
    }
}

public interface ILibraryService {

}

请注意语法更改,这些更改会显示问题中显示的原始代码的错误。

以下测试演示了如何测试 LibraryService.GetResponse 方法并断言预期行为

public async Task SampleTest() {
    //Arrange
    var content = new StringContent("{}", Encoding.UTF8, "application/json");
    var httpResponse = new HttpResponseMessage() {
        StatusCode = HttpStatusCode.OK,
        Content = content
    };

    var _mockRetryHttpRequest = new Mock<IRetryHttpRequest>();
    _mockRetryHttpRequest
        .Setup(_ => _.ExecuteAsync(It.IsAny<Func<HttpRequestMessage>>(), It.IsAny<HttpClient>(), It.IsAny<int>()))
        .ReturnsAsync(httpResponse);

    var lgService = new LibraryService(_mockRetryHttpRequest.Object);

    //Act
    var response = await lgService.GetResponseForSearch(new SearchRequest(), null);

    //Assert
    response.Should().NotBeNull();
}

FluentAssertions 用于断言预期行为。

一些注意事项

  • 仅向被测对象提供了执行测试实际需要的依赖项。这意味着 mock

  • 实际上不需要 HttpClient
  • 被测方法也需要等待才能得到断言响应

  • 因为我无权访问您的测试数据,所以我使用一个空的 JSON 对象“{}”来表示响应的内容以允许 JsonConvert工作

  • 然后我建议您查看正在反序列化的测试数据。这可能是失败点,因为这是唯一可以将 result 设置为 null 的地方,因为它是在函数顶部初始化的。

你不应该让你的测试方法异步。使其同步,然后执行以下操作而不是等待:

var task = lgService.GetResponseForSearch(new SearchRequest(), null);

task.Wait();

var response = task.Result;

response.Should().NotBeNull();