C# 如何为使用包装器的Azure函数编写单元测试?

C# 如何为使用包装器的Azure函数编写单元测试?,c#,unit-testing,.net-core,dependency-injection,azure-functions,C#,Unit Testing,.net Core,Dependency Injection,Azure Functions,我在所有Azure函数上使用包装器类: public interface IFunctionWrapper { Task<IActionResult> Execute(HttpRequest req, ExecutionContext context, Func<Task<IActionResult>> azureFunction); } public class FunctionWrapper : IFunctionWrapper { pr

我在所有Azure函数上使用包装器类:

public interface IFunctionWrapper
{
    Task<IActionResult> Execute(HttpRequest req, ExecutionContext context, Func<Task<IActionResult>> azureFunction);
}

public class FunctionWrapper : IFunctionWrapper
{
    private readonly ILogger _log;

    public FunctionWrapper(ILogger<FunctionWrapper> log)
    {
        _log = log;
    }

    public async Task<IActionResult> Execute(HttpRequest req, ExecutionContext context, Func<Task<IActionResult>> azureFunction)
    {
        try
        {
            // Log few extra information to Application Insights

            // Do authentication

            return await azureFunction();
        }
        catch (Exception ex)
        {    
            // Return a custom error response
        }
    }
}
公共接口IFunctionWrapper
{
任务执行(HttpRequest请求、ExecutionContext上下文、Func azureFunction);
}
公共类FunctionWrapper:IFFunctionWrapper
{
私有只读ILogger_日志;
公共函数包装器(ILogger日志)
{
_log=log;
}
公共异步任务执行(HttpRequest请求、ExecutionContext上下文、Func azureFunction)
{
尝试
{
//在Application Insights中记录一些额外信息
//进行身份验证
返回等待azureFunction();
}
捕获(例外情况除外)
{    
//返回自定义错误响应
}
}
}
下面是它在函数中的用法:

public class MyFunctions
{
    private readonly IFunctionWrapper _functionWrapper;

    public MyFunctions(IFunctionWrapper functionWrapper)
    {
        _functionWrapper = functionWrapper;
    }

    public async Task<IActionResult> GetPost(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
        ExecutionContext context,
        ILogger log)
    {
        return await _functionWrapper.Execute(req, context, async () =>
        {
            // Function code...
    
            return new JsonResult(post);
        });
    }
}
公共类MyFunctions
{
私有只读IFunctionWrapper\u functionWrapper;
公共MyFunctions(IFunctionWrapper函数包装器)
{
_functionWrapper=functionWrapper;
}
公共异步任务GetPost(
[HttpTrigger(AuthorizationLevel.Anonymous,“get”,Route=null)]HttpRequest请求,
ExecutionContext上下文,
ILogger日志)
{
return wait\u functionWrapper.Execute(req,context,async()=>
{
//函数代码。。。
返回新的JsonResult(post);
});
}
}

我正在尝试为这个GetPost函数编写单元测试。在这种情况下,如何模拟FunctionWrapper类?

模拟包装抽象所需的行为

下面的示例使用MOQ模拟包装器。注意模拟的设置

[TestClass]
public class MyFunctionsTests {
    [TestMethod]
    public async Task GetPost_Should_Execute_Wrapper() {
        //Arrange
        //mock the wrapper
        IFunctionWrapper wrapper = Mock.Of<IFunctionWrapper>();
        //configure the mocked wrapper to behave as expected when invoked
        Mock.Get(wrapper)
            .Setup(_ => _.Execute(It.IsAny<HttpRequest>(), It.IsAny<ExecutionContext>(), It.IsAny<Func<Task<IActionResult>>>()))
            .Returns((HttpRequest r, ExecutionContext c, Func<Task<IActionResult>> azureFunction) => 
                azureFunction()); //<-- invokes the delegate and returns its result

        MyFunctions function = new MyFunctions(wrapper);

        //these should be initialized as needed for the test
        HttpRequest req = null; 
        ExecutionContext ctx = null;
        ILogger log = Mock.Of<ILogger>();

        //Act
        IActionResult result = await function.GetPost(req, ctx, log);

        //Assert
        result.Should().NotBeNull();
        //verify that mocked wrapper was called
        Mock.Get(wrapper).Verify(_ => _.Execute(It.IsAny<HttpRequest>(), It.IsAny<ExecutionContext>(), It.IsAny<Func<Task<IActionResult>>>()));

        //...perform other assertions here
    }
}
[TestClass]
公共类MyFunctionsTests{
[测试方法]
公共异步任务GetPost_应_执行_包装器(){
//安排
//嘲笑包装纸
IFunctionWrapper=Mock.Of();
//将模拟包装器配置为在调用时按预期运行
Mock.Get(包装器)
.Setup(=>u.Execute(It.IsAny(),It.IsAny(),It.IsAny()))
.Returns((HttpRequest r、ExecutionContext c、Func azureFunction)=>
azureFunction();///执行(It.IsAny(),It.IsAny(),It.IsAny());
//…在此执行其他断言
}
}

原始问题中的代码省略了测试对象的大部分主体。这就是说,本示例基于最初提供的内容,用于创建用于创建上述测试的可复制示例

您根本不需要创建包装器接口:

  • HttpRequest
    是可模拟的:
  • ExecutionContext
    可以被模拟(或者只是一个POCO)
  • ILogger
    可以模拟
  • 使用依赖项注入来注入函数的依赖项(然后模拟这些依赖项)
  • 请记住,您确实只想测试参数验证和可能的解析是否正常工作

我知道我可以将Execute()中的函数代码放入另一个方法并测试该方法。但是我想知道,在这种情况下,我是否可以模拟包装器来直接测试函数。谢谢,@Nkosi。您的回答解决了我的问题:)。好吧,我确实需要创建包装器接口。Azure函数筛选器已过时,当出现异常时,它们不允许我为带有HttpTriggers的函数返回任何自定义响应。问题是如何使用实现的包装器测试函数。我的意思是我还需要测试函数内部的逻辑。我对模仿HttpRequest或Ilogger没有任何问题。