.NET 8孤立模型Durable Azure Function单元测试:Mock EntityMetadata遇阻
如何Mock .NET 8孤立工作者模型中DurableEntityClient返回的EntityMetadata
问题分析
你遇到的错误源于EntityMetadata<T>是密封类且无参构造函数缺失,Moq无法为其创建代理;同时测试中可能存在mock对象未正确注入被测函数的问题,导致实际调用了SDK的真实实现而非mock逻辑。
解决方案
方案一:封装抽象接口(推荐)
通过自定义抽象层隔离对Durable SDK的直接依赖,单元测试时只需Mock自己的接口,完全避开SDK内部类型的Mock难题。
- 定义抽象接口
public interface IEntityStateReader { Task<MyState?> GetEntityStateAsync(EntityInstanceId entityId, CancellationToken cancellationToken = default); }
- 实现真实逻辑
public class DurableEntityStateReader : IEntityStateReader { private readonly DurableTaskClient _durableTaskClient; public DurableEntityStateReader(DurableTaskClient durableTaskClient) { _durableTaskClient = durableTaskClient; } public async Task<MyState?> GetEntityStateAsync(EntityInstanceId entityId, CancellationToken cancellationToken = default) { var entity = await _durableTaskClient.Entities.GetEntityAsync<MyType>(entityId, cancellationToken); return await entity.State.Get(cancellationToken); } }
- 改造被测函数
将DurableTaskClient替换为注入的抽象接口:
public class MyFunction { private readonly IEntityStateReader _entityStateReader; // 通过构造函数注入抽象接口 public MyFunction(IEntityStateReader entityStateReader) { _entityStateReader = entityStateReader; } public async Task DoTheThing(string messageJson) { var message = JsonConvert.DeserializeObject<MyMessage>(messageJson); var entityId = new EntityInstanceId(nameof(MyType), message.ContextId.ToString()); var state = await _entityStateReader.GetEntityStateAsync(entityId); // 后续业务逻辑 } }
- 编写单元测试
直接Mock自定义接口,无需处理SDK的复杂类型:
[Theory] [AutoMockData] public async Task MyTest( [Frozen] Mock<IEntityStateReader> stateReaderMock, MyFunction function, MyMessage message, MyState expectedState) { // Arrange var entityId = new EntityInstanceId(nameof(MyType), message.ContextId.ToString()); stateReaderMock .Setup(r => r.GetEntityStateAsync(entityId, It.IsAny<CancellationToken>())) .ReturnsAsync(expectedState); // Act await function.DoTheThing(JsonConvert.SerializeObject(message)); // Assert // 验证业务逻辑的预期结果 stateReaderMock.Verify(r => r.GetEntityStateAsync(entityId, It.IsAny<CancellationToken>()), Times.Once); }
方案二:直接构造EntityMetadata并Mock内部状态
如果不想封装接口,可以直接构造EntityMetadata<T>实例,同时Mock其内部的EntityState<T>来模拟异步返回。
- 调整测试代码
[Theory] [AutoMockData] public async Task MyTest( MyFunction function, MyMessage message, MyState expectedState) { // Arrange var entityId = new EntityInstanceId(nameof(MyType), message.ContextId.ToString()); // Mock EntityState<MyType>,模拟Get()方法的返回 var entityStateMock = new Mock<EntityState<MyType>>(); entityStateMock .Setup(s => s.Get(It.IsAny<CancellationToken>())) .ReturnsAsync(expectedState); // 直接构造EntityMetadata实例,传入mock的状态 var entityMetadata = new EntityMetadata<MyType>(entityId, entityStateMock.Object); // Mock DurableEntityClient和DurableTaskClient var entityClientMock = new Mock<DurableEntityClient>("test"); entityClientMock .Setup(d => d.GetEntityAsync<MyType>(entityId, It.IsAny<CancellationToken>())) .ReturnsAsync(entityMetadata); var durableClientMock = new Mock<DurableTaskClient>("test"); durableClientMock.Setup(d => d.Entities).Returns(entityClientMock.Object); // Act:确保传入mock的DurableTaskClient await function.DoTheThing(JsonConvert.SerializeObject(message), durableClientMock.Object); // Assert // 验证业务逻辑的预期结果 entityClientMock.Verify(d => d.GetEntityAsync<MyType>(entityId, It.IsAny<CancellationToken>()), Times.Once); }
关键注意点
- 方案一遵循依赖倒置原则,代码解耦更彻底,后续SDK版本更新时测试代码无需修改,是长期维护的最优选择。
- 方案二中要确保mock的
DurableTaskClient被正确传入被测函数,避免使用AutoFixture自动生成的默认实例(如果用了[AutoMockData],需要替换掉对应的参数)。
内容的提问来源于stack exchange,提问作者kdeez
相关产品推荐
相关产品推荐

