使用AutoNSubstituteClassData时xUnit Theory仅执行一次的原因
问题:AutoNSubstituteClassData搭配xUnit Theory仅执行一次
背景与自定义实现
我使用AutoFixture搭配NSubstitute和xUnit,实现了以下自定义数据属性:
public class AutoNSubstituteDataAttribute : AutoDataAttribute { public AutoNSubstituteDataAttribute() : base(() => new Fixture().Customize(new AutoNSubstituteCustomization())) { } } public class AutoNSubstituteClassDataAttribute : CompositeDataAttribute { public AutoNSubstituteClassDataAttribute(Type @class) : base( new ClassDataAttribute(@class), new AutoNSubstituteDataAttribute()) { } }
问题现象
使用[AutoNSubstituteClassData]时,测试参数注入正常(包括自动模拟的参数),但Theory仅执行一次,无视IEnumerable的元素数量:
// 问题:TestClass1返回2个测试用例,但测试仅执行一次 [Theory] [AutoNSubstituteClassData(typeof(TestClass1))] public void AutoNSubstituteClassData_AutoFixture_Injects(string dummy, string actualConcrete, ITestInterface1 actualMock) { dummy.Should().Be("dummy"); AssertInjected(actualConcrete, actualMock); } public class TestClass1 : IEnumerable<object[]> { public IEnumerator<object[]> GetEnumerator() { yield return new object[] {"dummy"}; yield return new object[] {"dummy"}; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
原因分析
你使用CompositeDataAttribute组合ClassDataAttribute和AutoNSubstituteDataAttribute的方式有误:
CompositeDataAttribute的默认逻辑是合并多个独立数据源的完整参数集,而非用一个数据源提供部分参数、另一个补充剩余参数。ClassDataAttribute返回的两个参数数组仅包含1个元素,与测试方法需要的3个参数不匹配,会被xUnit直接跳过。AutoNSubstituteDataAttribute返回1个完整的3参数数组,xUnit仅执行这一次测试——你观察到dummy是指定的"dummy"属于巧合,本质上原属性逻辑并未正确组合ClassData的参数和AutoFixture的补充参数。
解决方法
不要使用CompositeDataAttribute,而是自定义继承自ClassDataAttribute的属性,在GetData方法中主动用AutoFixture补充剩余参数:
public class AutoNSubstituteClassDataAttribute : ClassDataAttribute { private readonly IFixture _fixture; public AutoNSubstituteClassDataAttribute(Type @class) : base(@class) { _fixture = new Fixture().Customize(new AutoNSubstituteCustomization()); } public override IEnumerable<object[]> GetData(MethodInfo testMethod) { var parameters = testMethod.GetParameters(); var classData = base.GetData(testMethod).ToList(); foreach (var data in classData) { var missingParamCount = parameters.Length - data.Length; if (missingParamCount <= 0) { yield return data; continue; } // 生成需要补充的参数 var additionalParams = new object[missingParamCount]; for (int i = 0; i < missingParamCount; i++) { var paramType = parameters[data.Length + i].ParameterType; additionalParams[i] = _fixture.Create(paramType); } // 合并原参数与补充参数 yield return data.Concat(additionalParams).ToArray(); } } }
这个实现的核心逻辑:
- 先获取
ClassDataAttribute提供的所有基础参数集。 - 对每个参数集,计算需要AutoFixture补充的参数数量。
- 用AutoFixture生成对应类型的补充参数,与原参数合并成完整的参数数组。
- 返回所有完整的参数集,确保xUnit能识别到所有测试用例并执行。
验证效果
修改后,TestClass1返回的两个参数集会被分别补充完整,Theory会执行两次,每次的dummy参数都是指定的"dummy",同时actualConcrete和actualMock由AutoFixture自动生成和模拟。
内容的提问来源于stack exchange,提问作者g.pickardou
相关产品推荐
相关产品推荐

