【问题标题】:Func<HttpRequestMessage> related Mocking Questions for Unit TestsFunc<HttpRequestMessage> 相关单元测试的模拟问题
【发布时间】:2019-05-24 18:00:07
【问题描述】:

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

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

【问题讨论】:

  • 我认为你应该像var response = await lgService.GetResponse一样等待它
  • @CodingYoshi 仍然没有解决问题:(
  • 在 mock 的 Returns 部分放置一个断点,看看它是否被命中。
  • 使用.Returns( async () =&gt; ...
  • @superninja 首先,您发布的代码无法编译。要获得帮助,请始终确保发布的代码可以编译。其次,一旦编译错误得到修复,一切正常,httpResponseMessage 不为空。这意味着您正在做一些您没有告诉我们的事情,并且它没有在发布的代码中体现出来。如果您想查看我的示例,请告诉我,我会将其发布为您查看的答案。

标签: c# unit-testing asp.net-core moq xunit


【解决方案1】:

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

假设如下

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 用于断言预期的行为。

注意事项

  • 仅将完成测试所需的依赖项提供给被测对象。这意味着模拟实际上不需要HttpClient

  • 还需要等待被测方法才能获得断言响应。

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

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

【讨论】:

  • 感谢您的回答,但是response 变量仍然返回null,这真的很奇怪...
  • @superninja 我测试了这段代码,它在运行时有效。可以用实际正在做的事情更新您的问题,以便我可以重现您的情况并找到解决方案。
  • @superninja 我对我的测试进行了相同的更改,它按预期工作。因为我无法访问您的测试数据,所以与我的测试的唯一区别是我使用了一个空的 JSON 对象 "{}" 来表示响应的内容并允许 JsonConvert 工作。
  • @superninja 我建议您查看正在反序列化的测试数据。这可能是失败点,因为这是结果可以设置为 null 的唯一地方,因为它是在函数顶部初始化的。
  • 很有趣.. 我不认为它是反序列化,因为它是在得到httpResponseMessage 的结果之后。我认为我的问题更多是针对_retryHttpRequest.ExecuteAsync(),因为它给httpResponseMessage 一个null 值。测试结果取决于httpResponseMessage..
【解决方案2】:

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

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

task.Wait();

var response = task.Result;

response.Should().NotBeNull();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-28
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    相关资源
    最近更新 更多