1

I'm trying to create a unit test for azure function and getting Unsupported expression: Non-overridable members may not be used in setup / verification expressions.

Here is my Function

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

And here is my Repository where I'm injecting the 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
          }
     }

And here is my Test. I'm using Xunit and 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
}

But getting the exception

System.NotSupportedException : Unsupported expression: o => o.GetEmployeeByName(It.IsAny<string>())   Non-overridable members (here: EmpRepo.GetEmployeeByName) may not be used in setup / verification expressions.
  • This is an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Inject the interface instead of the concrete class. Secondly the test should use `async Task` and not `async void`. Finally, the repo should be a typed client instead of using the `IHttpClientFactory` – Nkosi Jun 28 '21 at 16:22
  • @Nkosi what do you mean by inject the interface. When I tried ```var empRepo= new Mock(httpClientFactory.Object);```, I'm getting 'Constructor arguments cannot be passed for interface mocks.' – Michael Edwards Jun 28 '21 at 16:43

1 Answers1

2

This is an XY problem.

Inject the interface instead of the concrete class into the function.

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

        //...
    }
}

the repo should be a typed client instead of injecting 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
    }
 }

and configured accordingly

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

Finally the test should use async Task and not 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
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I already tried that, When I register the httpclient an inject it into the class, the base url is always coming up as null. `builder.Services.AddHttpClient().ConfigureHttpClient(c => { c.BaseAddress = new Uri("api url"); });` – Michael Edwards Jun 28 '21 at 17:07
  • @MichaelEdwards check update on configuring the typed client. – Nkosi Jun 28 '21 at 17:09
  • Still getting the base url as null. `builder.Services.AddSingleton(); builder.Services.AddHttpClient(c => { c.BaseAddress = new Uri("api url");});` – Michael Edwards Jun 28 '21 at 17:15
  • Then set it in the constructor of the repo – Nkosi Jun 28 '21 at 17:18