You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8孤立模型Durable Azure Function单元测试:Mock EntityMetadata遇阻

如何Mock .NET 8孤立工作者模型中DurableEntityClient返回的EntityMetadata

问题分析

你遇到的错误源于EntityMetadata<T>是密封类且无参构造函数缺失,Moq无法为其创建代理;同时测试中可能存在mock对象未正确注入被测函数的问题,导致实际调用了SDK的真实实现而非mock逻辑。

解决方案

方案一:封装抽象接口(推荐)

通过自定义抽象层隔离对Durable SDK的直接依赖,单元测试时只需Mock自己的接口,完全避开SDK内部类型的Mock难题。

  1. 定义抽象接口
public interface IEntityStateReader
{
    Task<MyState?> GetEntityStateAsync(EntityInstanceId entityId, CancellationToken cancellationToken = default);
}
  1. 实现真实逻辑
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);
    }
}
  1. 改造被测函数
    将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);
        
        // 后续业务逻辑
    }
}
  1. 编写单元测试
    直接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>来模拟异步返回。

  1. 调整测试代码
[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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:05:13