如何通过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
相关产品推荐
相关产品推荐

