Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在ConfigureServices中使用自定义HttpClient和Polly策略对核心API控制器进行单元测试_C#_Unit Testing_Asp.net Core_.net Core_Asp.net Core Webapi - Fatal编程技术网

C# 在ConfigureServices中使用自定义HttpClient和Polly策略对核心API控制器进行单元测试

C# 在ConfigureServices中使用自定义HttpClient和Polly策略对核心API控制器进行单元测试,c#,unit-testing,asp.net-core,.net-core,asp.net-core-webapi,C#,Unit Testing,Asp.net Core,.net Core,Asp.net Core Webapi,使用Polly和HttpClient时,我在执行单元测试时遇到问题 具体而言,Polly和HttpClient用于ASP.NET核心Web API控制器,链接如下: 问题(1和2)在底部指定。我想知道这是否是使用Polly和HttpClient的正确方法 配置服务 配置Polly策略并单独指定自定义HttpClient,CustomHttpClient public void ConfigureServices(IServiceCollection services) { serv

使用
Polly
HttpClient
时,我在执行单元测试时遇到问题

具体而言,Polly和HttpClient用于ASP.NET核心Web API控制器,链接如下:

问题(1和2)在底部指定。我想知道这是否是使用
Polly
HttpClient
的正确方法

配置服务

配置Polly策略并单独指定自定义HttpClient,
CustomHttpClient

  public void ConfigureServices(IServiceCollection services)
{
   services.AddHttpClient();
   services.AddHttpClient<HttpClientService>()                                
                .AddPolicyHandler((service, request) =>
                    HttpPolicyExtensions.HandleTransientHttpError()
                        .WaitAndRetryAsync(3,
                            retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))
                );
 }
HttpClientService

CarController
类似,
HttpClientService
在单元测试方面存在问题,因为无法模拟
HttpClient
的PostAsync
HttpClient
由框架自动注入,无需显式注册

 public class HttpClientService
{
    private readonly HttpClient _client;
    public HttpClientService(HttpClient client)
    {
        _client = client;
    }

    public async Task<HttpStatusCode> PostAsync(string url, Dictionary<string, string> headers, string body)
    {
        using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
        {
            foreach (var keyValue in headers)
            {
                content.Headers.Add(keyValue.Key, keyValue.Value);
            }

            var response = await _client.PostAsync(url, content);

            response.EnsureSuccessStatusCode();
            return response.StatusCode;
        }

    }
问题2

单元测试:类似于
HttpClientService
Moq
无法模拟
HttpClient
PostAsync
方法

  public class HttpClientServiceTests
{
        [Fact]
           public void Post_Returns200()
        {

            var httpClientMock = new Mock<HttpClient>();

            //System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member
        httpClientMock.Setup(hc => hc.PostAsync("", It.IsAny<HttpContent>()))
            .Returns(Task.FromResult(new HttpResponseMessage()));
            }

}
公共类HttpClientServiceTests
{
[事实]
公共无效邮政返回200()
{
var httpClientMock=new Mock();
//System.NotSupportedException:非虚拟(在VB中可重写)成员上的设置无效
httpClientMock.Setup(hc=>hc.PostAsync(“,It.IsAny()))
.Returns(Task.FromResult(new HttpResponseMessage());
}
}
ASP.NET核心API 2.2

更新

更正了CustomHttpClient到HttpClientService的输入错误

更新2


对于问题2

假设
CustomHttpClient
是一个输入错误,
HttpClientService
是实际的依赖关系,控制器与实现问题紧密耦合,正如您已经经历过的那样,很难单独进行测试(单元测试)

将这些具体化封装在抽象背后

public interface IHttpClientService {
    Task<HttpStatusCode> PostAsync(string url, Dictionary<string, string> headers, string body);
}


public class HttpClientService : IHttpClientService {
    //...omitted for brevity
}
公共接口IHttpClientService{
任务PostAsync(字符串url、字典标题、字符串正文);
}
公共类HttpClientService:IHttpClientService{
//…为简洁起见省略
}
可以在测试时替换

重构控制器,使其依赖于抽象,而不是具体的实现

public class CarController : ControllerBase {
    private readonly ILog _logger;
    private readonly IHttpClientService httpClientService; //<-- TAKE NOTE
    private readonly IOptions<Config> _config;

    public CarController(ILog logger, IHttpClientService httpClientService, IOptions<Config> config) {
        _logger = logger;
        this.httpClientService = httpClientService;
        _config = config;
    }

    //...omitted for brevity

}
public void ConfigureServices(IServiceCollection services) {
    services.AddHttpClient();
    services
        .AddHttpClient<IHttpClientService, HttpClientService>() //<-- TAKE NOTE
        .AddPolicyHandler((service, request) =>
            HttpPolicyExtensions.HandleTransientHttpError()
                .WaitAndRetryAsync(3,
                    retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))
        );
}
公共类CarController:ControllerBase{
专用只读ILog_记录器;
私有只读IHttpClientService httpClientService;//TimeSpan.FromSeconds(Math.Pow(2,retryCount)))
);
}
这需要对代码进行重构,使其更易于测试

下面显示了控制器现在如何进行隔离单元测试

public class CarControllerTests {
    private readonly Mock<ILog> _logMock;
    private readonly Mock<IHttpClientService> _httpClientServiceMock;
    private readonly Mock<IOptions<Config>> _optionMock;
    private readonly CarController _sut;


    public CarControllerTests()  //runs for each test method 
    {
        _logMock = new Mock<ILog>();
        _httpClientServiceMock = new Mock<IHttpClientService>();
        _optionMock = new Mock<IOptions<Config>>();
        _sut = new CarController(_logMock.Object, _httpClientServiceMock.Object, _optionMock.Object);
    }

    [Fact]
    public async Task Post_Returns200() {
        //Arrange
        _httpClientServiceMock
            .Setup(_ => _.PostAsync(
                It.IsAny<string>(),
                It.IsAny<Dictionary<string, string>>(),
                It.IsAny<string>())
            )
            .ReturnsAsync(HttpStatusCode.OK);

        //Act

        //...omitted for brevity

        //...
    }
}
公共类CarControllerTests{
私有只读Mock_logMock;
私有只读模拟\u httpClientServiceMock;
私有只读Mock_optionMock;
专用只读CarController_sut;
public CarControllerTests()//为每个测试方法运行
{
_logMock=newmock();
_httpClientServiceMock=新建Mock();
_optionMock=newmock();
_sut=新的CarController(_logMock.Object、_httpClientServiceMock.Object、_optionMock.Object);
}
[事实]
公共异步任务Post_Returns200(){
//安排
_httpClientServiceMock
.Setup(=>.PostAsync(
It.IsAny(),
It.IsAny(),
It.IsAny())
)
.ReturnsAsync(HttpStatusCode.OK);
//表演
//…为简洁起见省略
//...
}
}
请注意,如何不再需要
HttpClient
来完成测试


控制器需要确保为测试流程安排了其他依赖项,但这目前超出了问题的范围。

控制器与实现问题紧密耦合,正如您已经经历过的,很难单独测试(单元测试)。将这些具体化封装在测试时可以替换的抽象后面。在本例中,
CustomHttpClient
HttpClientService
吗?错别字?更正了错别字,请查看我的更新和对您答案的评论。谢谢。太好了,那HttpClientServiceTests呢?这是由于HttpClient无法被Moq模拟而导致的问题2。@Pingpong review可获得HttpClient依赖性测试的帮助
public void ConfigureServices(IServiceCollection services) {
    services.AddHttpClient();
    services
        .AddHttpClient<IHttpClientService, HttpClientService>() //<-- TAKE NOTE
        .AddPolicyHandler((service, request) =>
            HttpPolicyExtensions.HandleTransientHttpError()
                .WaitAndRetryAsync(3,
                    retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))
        );
}
public class CarControllerTests {
    private readonly Mock<ILog> _logMock;
    private readonly Mock<IHttpClientService> _httpClientServiceMock;
    private readonly Mock<IOptions<Config>> _optionMock;
    private readonly CarController _sut;


    public CarControllerTests()  //runs for each test method 
    {
        _logMock = new Mock<ILog>();
        _httpClientServiceMock = new Mock<IHttpClientService>();
        _optionMock = new Mock<IOptions<Config>>();
        _sut = new CarController(_logMock.Object, _httpClientServiceMock.Object, _optionMock.Object);
    }

    [Fact]
    public async Task Post_Returns200() {
        //Arrange
        _httpClientServiceMock
            .Setup(_ => _.PostAsync(
                It.IsAny<string>(),
                It.IsAny<Dictionary<string, string>>(),
                It.IsAny<string>())
            )
            .ReturnsAsync(HttpStatusCode.OK);

        //Act

        //...omitted for brevity

        //...
    }
}