Azure 函数单元测试模拟 HttpClientFactory

Azure Function UnitTesting Mock HttpClientFactory

我正在尝试为 azure 函数创建单元测试并获取不受支持的表达式: 不可覆盖成员不能用于设置/验证表达式。

这是我的函数

public class EmployeeFunction
    {
        private readonly EmpRepo _repo;

        public EmployeeFunction(EmpRepo repo) 
        {
            _repo = repo;
        }

        [FunctionName("EmployeeFunctionTrigger")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "EmployeeFunction/{empName}")] HttpRequest req, string empName, ILogger log)
        {
            //code
            return _repo.GetEmployeeByName(empName);
        }
     }

这是我的存储库,我在其中注入 IHttpClientFactory

    public class EmpRepo : IEmpRepo 
    {
        private readonly IHttpClientFactory _httpClientFactory;
        public EmpRepo (IHttpClientFactory clientFactory)
        {
            _httpClientFactory = clientFactory;
        }

         public async Task<Employee> GetEmployeeByName(string name)
         {
            using HttpClient _httpClient = httpClientFactory.CreateClient("EmpClient"){
             //code
          }
     }

这是我的测试。我正在使用 Xunit 和 Moq

public class EmpFunctionTest
{
    [Fact]
        public async Task Http_trigger_should_return_known_string()
        {
            // Arrange  
            var httpClientFactory = new Mock<IHttpClientFactory>();
            var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
            var fixture = new Fixture();

            mockHttpMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent(fixture.Create<String>()),
                });

            var client = new HttpClient(mockHttpMessageHandler.Object);
            client.BaseAddress = fixture.Create<Uri>();
            httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
       
         

            var empRepo= new Mock<EmpRepo>(httpClientFactory.Object);
            empRepo.Setup(o => o.GetEmployeeByName(It.IsAny<string>()))
              // prepare the expected response of the mocked http call
              .ReturnsAsync(new Employee()
              {
                  Name= "Test",
                  ID= 12345,
              })
              .Verifiable();

              var EmpFuc= new EmployeeFunction(empRepo.Object);
            //code for act and assert
}

但是得到异常

System.NotSupportedException : Unsupported expression: o => o.GetEmployeeByName(It.IsAny<string>())   Non-overridable members (here: EmpRepo.GetEmployeeByName) may not be used in setup / verification expressions.

这是一个XY problem

将接口而不是具体 class 注入函数。

public class EmployeeFunction {
    private readonly IEmpRepo repo;

    public EmployeeFunction(IEmpRepo repo) {
        this.repo = repo;
    }

    [FunctionName("EmployeeFunctionTrigger")]
    public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "EmployeeFunction/{empName}")] HttpRequest req, string empName, ILogger log) {
        //code
        var x = await repo.GetEmployeeByName(empName);

        //...
    }
}

repo 应该是类型化的客户端而不是注入 IHttpClientFactory

public class EmpRepo : IEmpRepo {
    private readonly HttpClient client;

    public EmpRepo (HttpClient client) {
        this.client = client;
        //this.client.BaseAddress = new Uri("api url"); //(optional)
    }

     public async Task<Employee> GetEmployeeByName(string name) {
         //..use client here
    }
 }

并进行相应配置

builder.Services.AddHttpClient<IEmpRepo, EmpRepo>(c => {
    // c.BaseAddress = new Uri("api url"); // as needed
});

最终测试应该使用 async Task 而不是 async void

public class EmpFunctionTest {
    [Fact]
    public async Task Http_trigger_should_return_known_string() {
        // Arrange  
        var fixture = new Fixture().Customize(new AutoMoqCustomization());

        Mock<IEmpRepo> empRepo = fixture.Freeze<Mock<IEmpRepo>>();
        empRepo
            .Setup(o => o.GetEmployeeByName(It.IsAny<string>()))
            .ReturnsAsync(new Employee() {
                Name = "Test",
                ID = 12345,
            })
            .Verifiable();

        EmployeeFunction subject = fixture.Create<EmployeeFunction>();

        //...code for act and assert
    }
}