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

AutoFixture AutoMoq中,注入Mock依赖的CallBase为何无法生效?

问题:AutoFixture中给Mock仓储启用CallBase是否可行?

我用AutoFixture创建通过构造函数注入仓储依赖的服务时,fixture.Create<>会自动生成Mock仓储。现在想给这个Mock启用CallBase,让它调用底层类的实际方法(相关方法已设为虚方法)。但AutoFixture的测试返回null,仓储的GetPersonName方法从未通过CallBase调用;而直接用Moq的测试却能正常运行。

相关代码

服务类

public class PersonService : IPersonService {
    private readonly IPersonRepository _personRepository;

    public PersonService(IPersonRepository personRepository) {
        _personRepository = personRepository;
    }

    public virtual string GetPersonName(int id) => _personRepository.GetPersonName(id);
}

仓储类

public class PersonRepository : IPersonRepository {
    public virtual string GetPersonName(int id) => Data.People[id].FirstName!;
}

数据类

public static class Data {
    public static List<Person> People { get; } = new() {
        new Person { FirstName = "Jack" },
        new Person { FirstName = "Jill" }
    };
}

Person类

public class Person {
    public string? FirstName { get; set; }
}

失败的AutoFixture测试

[Fact]
public void AutoFixture_Test_CallBase() {
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());

    var mockPersonRepository = fixture.Freeze<Mock<PersonRepository>>();
    mockPersonRepository.Setup(r => r.GetPersonName(It.IsAny<int>())).CallBase();

    var sut = fixture.Create<PersonService>();
    var result = sut.GetPersonName(1);

    Assert.Equal(Data.People[1].FirstName, result);
}

成功的直接Moq测试

[Fact]
public void Moq_Test_CallBase() {
    var mockPersonRepository = new Mock<PersonRepository>();
    mockPersonRepository.Setup(r => r.GetPersonName(It.IsAny<int>())).CallBase();

    var sut = new PersonService(mockPersonRepository.Object);
    var result = sut.GetPersonName(1);

    Assert.Equal(Data.People[1].FirstName, result);
}

原因分析

问题出在AutoFixture的AutoMoqCustomization默认行为:它会为接口类型创建Mock实例。你的PersonService依赖的是IPersonRepository接口,所以AutoFixture实际生成并注入的是Mock<IPersonRepository>.Object,而非你通过Freeze配置的Mock<PersonRepository>.Object。这就导致你设置的CallBase完全没被用到,自然返回null。

解决方案

方案1:自定义接口Mock的底层实现

通过Register方法,指定IPersonRepository的实例来自基于PersonRepository创建的Mock,同时启用CallBase:

[Fact]
public void AutoFixture_Test_CallBase_Fixed()
{
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true });

    // 手动注册IPersonRepository的Mock实现,基于PersonRepository并启用CallBase
    fixture.Register<IPersonRepository>(() =>
    {
        var mock = new Mock<PersonRepository>();
        mock.Setup(r => r.GetPersonName(It.IsAny<int>())).CallBase();
        return mock.Object;
    });

    var sut = fixture.Create<PersonService>();
    var result = sut.GetPersonName(1);

    Assert.Equal(Data.People[1].FirstName, result);
}

方案2:让服务依赖具体仓储类(如果设计允许)

修改PersonService的构造函数,直接依赖PersonRepository而非接口:

public class PersonService : IPersonService {
    private readonly PersonRepository _personRepository;

    public PersonService(PersonRepository personRepository) {
        _personRepository = personRepository;
    }

    public virtual string GetPersonName(int id) => _personRepository.GetPersonName(id);
}

此时测试代码无需大幅修改,Freeze的Mock<PersonRepository>会被正确注入:

[Fact]
public void AutoFixture_Test_CallBase_Fixed_WithConcreteDependency()
{
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());

    var mockPersonRepository = fixture.Freeze<Mock<PersonRepository>>();
    mockPersonRepository.Setup(r => r.GetPersonName(It.IsAny<int>())).CallBase();

    var sut = fixture.Create<PersonService>();
    var result = sut.GetPersonName(1);

    Assert.Equal(Data.People[1].FirstName, result);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 14:55:15