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

时间:2020-10-21 07:03:19

标签: 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
{
    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
        }
    }
}

这是函数中的用法:

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

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

2 个答案:

答案 0 :(得分:2)

模拟包装抽象的所需行为。

以下示例使用最小起订量来模拟包装器。注意模拟的设置

[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
    }
}

原始问题中的代码省略了测试对象的大部分身体。话虽如此,该示例基于最初提供的内容,用于创建可重复的示例,该示例用于创建上面的测试

答案 1 :(得分:0)

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

  • HttpRequest是可模拟的:https://mahmutcanga.com/2019/12/13/unit-testing-httprequest-in-c/
  • ExecutionContext可以被模拟(或仅按原样使用POCO)
  • ILogger可以被模仿
  • 使用依赖注入来注入函数的依赖(然后模拟依赖)。
  • 请记住,您真的只想测试参数验证和可能的解析是否正确。