Durable Function编排器函数部分模拟测试技术咨询
问题
我希望测试Durable Function中编排器函数的行为,仅对依赖外部源的调用进行模拟,以此验证编排器函数的步骤组合逻辑是否正常工作。我无需对每个新增的活动触发器单独测试,目标是验证最终结果。现咨询以下问题:
- 这种测试方式是否可行?
- 能否在测试设置中获取一个活动触发器的执行结果,作为另一个活动触发器设置的输入?
当前未完成的测试方法
[Fact] public async Task TestOrchestrator() { // Setup var fileContent = Encoding.UTF8.GetBytes("this is some filecontent 😀"); var encodedFileContent = Encoding.Latin1.GetString(fileContent); const string runId = "changeme"; var loggerMock = new Mock<ILogger>(); var clientMock = new Mock<IDurableOrchestrationContext>(); clientMock .Setup(context => context.CallActivityAsync<byte[]>(nameof(GetBlobContentAsByteArray), runId)) .ReturnsAsync(fileContent); // I want EncodeToISO88591 to do it's thing instead of me having to return a fake value. // clientMock // .Setup(context => context.CallActivityAsync<string>(nameof(EncodeToISO88591), new DataObject<byte[]>(fileContent))) // .ReturnsAsync(encodedFileContent); // I want this to consume the value of EncodeToISO88591, not a fake one. clientMock .Setup(context => context.CallActivityAsync<bool>(nameof(UploadToSftp), encodedFileContent)) .ReturnsAsync(true); //Act await RunOrchestrator(clientMock.Object, loggerMock.Object); }
编排器函数代码
public record DataObject<T>(T Data); const string OrchestratorFunctionName = nameof(RunOrchestrator); const string EncodeFunctionName = nameof(EncodeToISO88591); const string GetBlobContentFunctionName = nameof(GetBlobContentAsByteArray); const string UploadToSftpFunctionName = nameof(UploadToSftp); [FunctionName(OrchestratorFunctionName)] public static async Task RunOrchestrator( [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger logger) { if (!context.IsReplaying) logger.LogInformation("Starting Orchestration"); var runId = context.GetInput<string>(); byte[] blobContent = await context.CallActivityAsync<byte[]>(GetBlobContentFunctionName, runId); string encodedContent = await context.CallActivityAsync<string>(EncodeFunctionName, new DataObject<byte[]>(blobContent)); bool sftpUploadSuccess = await context.CallActivityAsync<bool>(UploadToSftpFunctionName, encodedContent); if (!sftpUploadSuccess) logger.LogError("Failed to upload to sftp"); if (!context.IsReplaying) logger.LogInformation($"Completed Orchestration of instance {context.InstanceId}"); }
回答
这种测试方式完全可行
这种聚焦编排器流程逻辑、仅模拟外部依赖的思路是Durable Function测试的最佳实践之一。只需Mock依赖外部资源(如Blob存储、SFTP服务器)的活动函数,让纯逻辑处理的活动函数(比如EncodeToISO88591)真实执行,既能验证编排器的步骤顺序、数据传递逻辑,又避免了外部依赖带来的不稳定,完全符合你的测试目标。可以实现让一个活动的真实执行结果作为另一个活动的输入
不需要预先MockEncodeToISO88591的返回值,而是在MockCallActivityAsync时,匹配到该活动调用后直接执行真实逻辑,再将结果传递给后续的UploadToSftp调用。修改后的测试代码如下:
[Fact] public async Task TestOrchestrator() { // Setup var fileContent = Encoding.UTF8.GetBytes("this is some filecontent 😀"); const string runId = "changeme"; var loggerMock = new Mock<ILogger>(); var clientMock = new Mock<IDurableOrchestrationContext>(); // 模拟依赖外部的GetBlobContentAsByteArray活动 clientMock .Setup(context => context.CallActivityAsync<byte[]>(nameof(GetBlobContentAsByteArray), runId)) .ReturnsAsync(fileContent); // 让EncodeToISO88591真实执行,返回计算后的结果 clientMock .Setup(context => context.CallActivityAsync<string>(nameof(EncodeToISO88591), It.IsAny<DataObject<byte[]>>())) .ReturnsAsync((DataObject<byte[]> input) => { // 直接调用真实的编码逻辑 return Encoding.Latin1.GetString(input.Data); }); // 捕获EncodeToISO88591的返回值,用于后续验证 string actualEncodedContent = null; clientMock .Setup(context => context.CallActivityAsync<bool>(nameof(UploadToSftp), It.IsAny<string>())) .Callback<string>(content => actualEncodedContent = content) .ReturnsAsync(true); // Act await RunOrchestrator(clientMock.Object, loggerMock.Object); // Assert:验证传递给UploadToSftp的内容是正确的编码结果 var expectedEncodedContent = Encoding.Latin1.GetString(fileContent); Assert.Equal(expectedEncodedContent, actualEncodedContent); }
关键说明:
- 对
EncodeToISO88591使用It.IsAny<DataObject<byte[]>>()匹配任意输入,在ReturnsAsync中直接执行真实编码逻辑并返回结果。 - 对
UploadToSftp用Callback<string>捕获实际传入的参数,通过断言验证该参数是否符合预期,确保编排器正确传递了前一个活动的结果。 - 这种方式既隔离了外部依赖,又保留了纯逻辑活动的真实执行,完美验证编排器的流程和数据传递逻辑。
内容的提问来源于stack exchange,提问作者Marcus Nätteldal
相关产品推荐
相关产品推荐

