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
相关产品推荐
相关产品推荐

