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

如何通过AutoMoq实现具体类依赖的Mock及冻结实例?

问题

被测类CityEventListener的构造函数依赖具体类ManageLocationRepository(而非AutoMoq推荐的接口/抽象)。现有自定义ClassAutoMoqDataAttribute与AutoMoqDataAttribute配置虽能自动Mock该类依赖,但无法获取其**Frozen(冻结)**实例。相关代码如下:

被测类代码

public class CityEventListener : IEvent<LocationChangeEventData>
{
    private readonly ILocationAdapterCaller _locationAdapterCaller;
    private readonly ManageLocationRepository _managerLocationRepository;

    public CityEventListener(ILocationAdapterCaller locationAdapterCaller, ManageLocationRepository managerLocationRepository)
    {
        _locationAdapterCaller = locationAdapterCaller;
        _managerLocationRepository = managerLocationRepository;
    }

    public async Task<bool> ProcessEvent(LocationChangeEventData eventData)
    {
    }
}

测试用例代码

[Theory(DisplayName = "Valid value test")]
[ClassAutoMoqData(typeof(ValidValueTests))]
public async Task ProcessEvent_WithCreateOrUpdateOperation_CallsUpsertCityAndReturnsResult(ExpectedValueTestData<Parameters, bool> data,
[Frozen] Mock<ILocationAdapterCaller> locationAdapterCallerMock, [Frozen] Mock<ManageLocationRepository> managerLocationRepositoryMock,
CityEventListener sut)
{
    // fakes
    var cityDetail = _fixture.Build<CityDetail>()
                            .With(x => x.Id, data.Params.locationChangeEventData.Id).Create();

    // Arrange
    locationAdapterCallerMock.Setup(mock => mock.GetCityDetail(data.Params.locationChangeEventData.Id))
        .ReturnsAsync(cityDetail).Verifiable();

    managerLocationRepositoryMock
        .Setup(mock => mock.UpsertCity(cityDetail))
        .ReturnsAsync(data.ExpectedValue).Verifiable();

    var result = await sut.ProcessEvent(data.Params.locationChangeEventData);

    // Assert
    using (new AssertionScope())
    {
        Assert.IsType<bool>(result);
        Assert.Equal(data.ExpectedValue, result);
        locationAdapterCallerMock.Verify();
        managerLocationRepositoryMock.Verify();
    }
}

AutoMoq特性配置代码

public class ClassAutoMoqDataAttribute : CompositeDataAttribute
{
    public ClassAutoMoqDataAttribute(Type values)
        : base(new ClassDataAttribute(values), new AutoMoqDataAttribute())
    {
    }
}

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute() : base(() =>
    {
        var fixture = new Fixture().Customize(new CompositeCustomization(
            new AutoMoqCustomization() { ConfigureMembers = true, GenerateDelegates = true },
            new SupportMutableValueTypesCustomization()));

        fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => fixture.Behaviors.Remove(b));
        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        return fixture;
    })
    {
    }
}

如何调整配置,通过类AutoMoq特性实现具体类依赖的冻结Mock实例?


解决方案

AutoMoq默认仅为接口/抽象类生成Mock,具体类会直接实例化。要实现具体类的冻结Mock,需要调整AutoMoq的自定义配置,强制为目标具体类生成Mock并冻结,有两种可选方案:

方案一:全局配置所有具体类生成Mock

修改AutoMoqDataAttribute的Fixture初始化逻辑,自定义MockFactory使其支持为非接口/抽象类创建Mock:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute() : base(() =>
    {
        var fixture = new Fixture();

        var autoMoqCustomization = new AutoMoqCustomization
        {
            ConfigureMembers = true,
            GenerateDelegates = true,
            // 配置MockFactory接受非接口/抽象类,生成Mock实例
            MockFactory = new MockFactory(MockBehavior.Default)
            {
                DefaultValue = DefaultValue.Mock
            }
        };

        fixture.Customize(autoMoqCustomization);
        fixture.Customize(new SupportMutableValueTypesCustomization());

        // 替换递归行为,避免循环引用报错
        fixture.Behaviors.Remove(fixture.Behaviors.OfType<ThrowingRecursionBehavior>().Single());
        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        return fixture;
    })
    {
    }
}

此方案下,测试用例中的[Frozen] Mock<ManageLocationRepository>参数会自动绑定到Fixture创建的冻结Mock实例,无需修改测试代码。

方案二:仅为指定具体类生成Mock(精准控制)

如果不想全局修改,可在Fixture初始化时提前注册并冻结目标具体类的Mock实例:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute() : base(() =>
    {
        var fixture = new Fixture().Customize(new CompositeCustomization(
            new AutoMoqCustomization() { ConfigureMembers = true, GenerateDelegates = true },
            new SupportMutableValueTypesCustomization()));

        // 提前冻结ManageLocationRepository的Mock,并注入其实例到Fixture
        var repoMock = fixture.Freeze<Mock<ManageLocationRepository>>();
        fixture.Inject(repoMock.Object);

        fixture.Behaviors.Remove(fixture.Behaviors.OfType<ThrowingRecursionBehavior>().Single());
        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        return fixture;
    })
    {
    }
}

此方案下,测试用例中的[Frozen] Mock<ManageLocationRepository>参数会直接获取预先冻结的Mock实例,确保被测类使用的是同一个Mock对象。


内容的提问来源于stack exchange,提问作者Sanjay Soni

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:21:02