Durable Functions单元测试遇SystemNotSupported异常求助
我正在为Durable Function编排中的Activity函数编写单元测试,使用VS 2022创建的模板。尝试模拟ILogger时触发了System.NotSupportedException异常。
待测试函数代码
[Function(nameof(MyFunction))] public static string MyFunction([ActivityTrigger] MyParameters input, FunctionContext executionContext) { ILogger logger = executionContext.GetLogger("MyFunction"); logger.LogInformation("Testing MyFunction..."); return "It worked!"; }
测试代码(触发异常部分)
[Test] public async Task MyFunction_ValidParameters_ReturnsValidString() { // Arrange Mock<FunctionContext> funcContextMock = new Mock<FunctionContext>(); funcContextMock.Setup(s => s.GetLogger(It.IsAny<string>())) .Returns(Mock.Of<ILogger>); // Act string blobName = await MyClass.MyFunction(input, funcContextMock.Object); // Assert Assert.That(blobName, Is.Not.Null); Assert.That(blobName, Is.Not.Empty); Assert.That(blobName, Does.StartWith(input.ExtractName)); }
异常触发于设置GetLogger()时。我理解该异常,但未找到拦截Activity函数中Logger的方法,请问该如何实现?
另外,我了解到有人推荐使用依赖注入进行Durable Functions单元测试,能否修改现有代码实现?微软单元测试帮助页面中的方法签名与VS2022生成的差异是什么?
一、解决GetLogger模拟异常的方法
FunctionContext.GetLogger是扩展方法,Moq无法直接模拟扩展方法,这就是触发NotSupportedException的原因。可以通过模拟FunctionContext的LoggerProvider属性间接获取Logger:
修改后的测试代码
[Test] public void MyFunction_ValidParameters_ReturnsValidString() { // Arrange var loggerMock = new Mock<ILogger>(); var loggerProviderMock = new Mock<ILoggerProvider>(); loggerProviderMock.Setup(p => p.CreateLogger(It.IsAny<string>())) .Returns(loggerMock.Object); var funcContextMock = new Mock<FunctionContext>(); funcContextMock.Setup(c => c.LoggerProvider) .Returns(loggerProviderMock.Object); var input = new MyParameters { ExtractName = "test" }; // Act string result = MyClass.MyFunction(input, funcContextMock.Object); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.Not.Empty); // 原业务返回值为"It worked!",修正断言逻辑 Assert.That(result, Is.EqualTo("It worked!")); // 验证日志是否被正确调用 loggerMock.Verify(l => l.Log( LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("Testing MyFunction...")), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once); }
二、改用依赖注入改造现有代码
VS2022默认生成静态函数,依赖注入需要将函数改为实例方法,通过构造函数注入ILogger以解耦运行时上下文:
改造后的Activity函数代码
public class MyActivityFunctions { private readonly ILogger<MyActivityFunctions> _logger; // 构造函数注入ILogger public MyActivityFunctions(ILogger<MyActivityFunctions> logger) { _logger = logger; } [Function(nameof(MyFunction))] public string MyFunction([ActivityTrigger] MyParameters input) { _logger.LogInformation("Testing MyFunction..."); return "It worked!"; } }
对应的单元测试代码
[Test] public void MyFunction_ValidParameters_ReturnsValidString() { // Arrange var loggerMock = new Mock<ILogger<MyActivityFunctions>>(); var activityFunctions = new MyActivityFunctions(loggerMock.Object); var input = new MyParameters { ExtractName = "test" }; // Act string result = activityFunctions.MyFunction(input); // Assert Assert.That(result, Is.EqualTo("It worked!")); // 验证日志调用 loggerMock.Verify(l => l.Log( LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("Testing MyFunction...")), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once); }
这种方式彻底避免了对FunctionContext的依赖,单元测试逻辑更简洁,也符合现代.NET的依赖注入设计原则。
三、微软单元测试文档与VS2022模板的方法签名差异
微软官方文档中的Durable Functions单元测试示例,普遍采用实例方法+依赖注入模式:函数参数仅保留业务输入(如ActivityTrigger绑定的参数),所需的ILogger等服务通过构造函数注入,完全解耦运行时上下文。
而VS2022默认生成的Activity函数模板是静态方法,强制将FunctionContext作为参数传入,要求开发者通过上下文获取ILogger、配置等资源,这种写法更偏向快速上手,但会导致函数与运行时强绑定,降低可测试性。
内容的提问来源于stack exchange,提问作者jsmith

