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

使用Moq/AutoMoq与xUnit覆盖AutoFixture ICustomization注册问题

问题场景与代码

架构说明

层级结构:Controller→Service→ClientWrapper→Client,编写集成测试时Mock最底层的ICalendarServiceClient,通过调用Controller验证Client是否被正确调用。

CalendarControllerBuilder实现

internal class CalendarControllerBuilder
{
    public CalendarControllerBuilder()
    {
        CalendarClientMock = new Mock<ICalendarServiceClient>(MockBehavior.Strict);
    }

    public Mock<ICalendarServiceClient> CalendarClientMock { get; set; }

    public CalendarControllerBuilder With(Mock<ICalendarServiceClient> calendarClientWrapperMock)
    {
        CalendarClientMock = calendarClientWrapperMock;
        return this;
    }

    public CalendarController Create()
    {
        var calendarClientWrapperMock = new CalendarClientWrapper(CalendarClientMock.Object);
        
        var calenderService = new CalendarService(calendarClientWrapperMock);
        return new CalendarController(calenderService);
    }
}

自定义ICustomization配置

internal class CalenderControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register<CalendarController>(() =>
        {
            // ----- ICalendarServiceClient mock setups -----
            var calendarServiceClientMock = new Mock<ICalendarServiceClient>(MockBehavior.Strict);

            calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                                     .ReturnsAsync(fixture.Create<EventList>()).Verifiable();
            
            return new CalendarControllerBuilder()
                   .With(calendarServiceClientMock)
                   .Create();
        });
    }
}

AutoMoq数据属性

public class Attributes
{
    public class AutoMoqDataAttribute<T> : AutoDataAttribute where T : ICustomization, new()
    {
        public AutoMoqDataAttribute()
            : base(() => new Fixture()
                       .Customize(
                                  new CompositeCustomization(
                                                             new AutoMoqCustomization(),
                                                             new T())))
        {
        }
    }

    public class AutoMoqDataAttribute : AutoDataAttribute
    {
        public AutoMoqDataAttribute()
            : base(() => new Fixture()
                       .Customize(
                                  new AutoMoqCustomization()))
        {
        }
    }
}

正常运行的测试用例

[Theory]
[AutoMoqData<CalenderControllerCustomization>]
public async Task Test_GetAllEvents_ClientIsCalledCorrectlyAndReturnsCorrectData(
    IFixture fixture,
    [Frozen] Mock<ICalendarServiceClient> calendarServiceClientMock,
    CalendarController sut)
{
    // Arrange
    var startDate = fixture.Create<DateTimeOffset>();
    var endDate = fixture.Create<DateTimeOffset>();

    // Act
    var eventList = await sut.GetAllEvents(startDate, endDate);

    // Assert
    eventList.Events.Count.Should().Be(3); // fixture always create 3 of lists here
    calendarServiceClientMock.Verify();
}

问题描述

尝试在测试中覆盖ICalendarServiceClient Mock的返回数据时,验证失败:

[Theory]
[AutoMoqData<CalenderControllerCustomization>]
public async Task Test_GetAllEvents_ClientIsCalledCorrectlyAndReturnsCorrectData_Overridden(
    IFixture fixture,
    [Frozen] Mock<ICalendarServiceClient> calendarServiceClientMock,
    CalendarController sut)
{
    // Arrange
    var startDate = fixture.Create<DateTimeOffset>();
    var endDate = fixture.Create<DateTimeOffset>();

    var result = fixture.Build<EventList>()
                        .With(x => x.Events, fixture.CreateMany<Event>(5).ToList())
                        .Create();

    // override client mock setup:
    calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                             .ReturnsAsync(fixture.Build<EventList>()
                                                  .With(x => x.Events, fixture.CreateMany<Event>(5).ToList())
                                                  .Create()).Verifiable();

    // Act
    var eventList = await sut.GetAllEvents(startDate, endDate);

    // Assert
    eventList.Events.Count.Should().Be(5); // FAILS - IT GETS 3 AND NOT 5
    calendarServiceClientMock.Verify(); // THIS FAILS TOO IF I SET 3 IN ABOVE
}

无论是否使用[Frozen]属性,问题依旧。希望避免重复调用CalendarControllerBuilder以减少样板代码,请问遗漏了什么配置?


解决方案

问题核心在于测试中拿到的calendarServiceClientMock和Controller依赖链中实际使用的Mock不是同一个实例。

原CalenderControllerCustomization里手动创建了新的Mock<ICalendarServiceClient>实例并传入构建器,但AutoFixture注入到测试方法中的Mock是AutoMoq自动生成的另一个独立实例——两者毫无关联,所以测试中覆盖的配置根本不会被Controller调用到。

修复步骤:

  1. 修改Customization,复用AutoFixture管理的Mock实例
internal class CalenderControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        // 从Fixture中获取AutoMoq创建的Mock实例(自动冻结)
        var calendarServiceClientMock = fixture.Create<Mock<ICalendarServiceClient>>();
        calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                                 .ReturnsAsync(fixture.Create<EventList>())
                                 .Verifiable();

        // 注册Controller创建逻辑,复用同一个Mock实例
        fixture.Register<CalendarController>(() =>
            new CalendarControllerBuilder()
               .With(calendarServiceClientMock)
               .Create());
    }
}
  1. 调整测试方法,兼容Strict模式的覆盖逻辑
[Theory]
[AutoMoqData<CalenderControllerCustomization>]
public async Task Test_GetAllEvents_ClientIsCalledCorrectlyAndReturnsCorrectData_Overridden(
    IFixture fixture,
    Mock<ICalendarServiceClient> calendarServiceClientMock,
    CalendarController sut)
{
    // Arrange
    var startDate = fixture.Create<DateTimeOffset>();
    var endDate = fixture.Create<DateTimeOffset>();

    var customEventList = fixture.Build<EventList>()
                                .With(x => x.Events, fixture.CreateMany<Event>(5).ToList())
                                .Create();

    // 重置原有Setup,避免Strict模式下重复配置冲突
    calendarServiceClientMock.Reset();
    calendarServiceClientMock.Setup(m => m.GetEvents(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
                             .ReturnsAsync(customEventList)
                             .Verifiable();

    // Act
    var eventList = await sut.GetAllEvents(startDate, endDate);

    // Assert
    eventList.Events.Count.Should().Be(5);
    calendarServiceClientMock.Verify();
}

关键说明:

  • 使用fixture.Create<Mock<ICalendarServiceClient>>()让AutoMoq统一管理Mock实例,保证整个Fixture上下文里只有这一个Mock被复用。
  • 因为启用了MockBehavior.Strict,覆盖配置前必须调用Reset()清除原有Setup,否则会因重复配置抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 12:15:30