使用虚拟方法模拟具体 class
Mocking a concrete class with a virtual method
我想用虚拟方法测试一个 class 依赖于另一个 class 的 class。
class DepClass
{
public virtual string Get() => "";
}
class HostClass
{
private _c;
public Host(DepClass c){ _c = c; }
public string Magic() => _c.Get();
}
现在我想用 Autofixture + NSubstitute 测试 HostClass
。
我的期望:
Fixture.Freeze<DepClass>().Get().ReturnsForAnyArg("123");
var sut = Fixture.Create<HostClass>();
var res = sut.Magic(); //should be 123
事实上,当我执行 Freeze().Get().Returns()
时,真正的 Get
方法被调用。
如何自定义 Autofixture 来模拟所有虚方法?
不讨论接口与虚拟方法等会很好
更新
这不起作用:
Fixture.Freeze<DepClass>().Get().ReturnsForAnyArgs("123");
同时,这个有效:
Substitute.For<DepClass>().Get().ReturnsForAnyArgs("123");
除了回答
另一种可能不符合您需求的方法:在我的例子中,我希望只有直接依赖项可以有虚拟方法。较低级别的所有依赖项都将被模拟。
因此,我决定采用更具体的方式。下面的代码是一个示例,因此您可能需要在将其用于您的解决方案之前对其进行修改。
abstract class UnitTestBase<T>
{
protected IFixture Fixture { get; private set; }
protected IList<Type> ProxyTypes {get; private set;}
protected CreateSut(): T => Fixt.Create<T>();
[SetUp]
protected virtual Setup()
{
ProxyTypes = new List<Type>();
Fixture = new Fixture().Customize(new CompositeCustomization(
new AutoNSubstituteCustomization {
ConfigureMembers = true,
GenerateDelegates = true
})
);
SetupTypesToProxy();
Fixture.Customizations.Add(new SubstituteRelay(new ProxyExactTypesSpecification(ProxyTypes)));
}
protected virtual void SetupTypesToProxy()
=> typeof(T)
.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.SelectMany(ctorInfo => ctorInfo
.GetParameters()
.Select(paramInfo => paramInfo.ParameterType)
.Where(ShouldProxy))
.ForEach(t => ProxyTypes.Add(t));
private static bool ShouldProxy(Type type)
=> !type.GetTypeInfo().IsInterface && !type.GetTypeInfo().IsAbstract;
}
internal class ProxyExactTypesSpecification: IRequestSpecification
{
public ProxyExactTypesSpecification(
IEnumerable<Type> types
)
{
_proxyTypes = types ?? Type.EmptyTypes;
}
public bool IsSatisfiedBy(
object request
)
{
Argument.NotNull(request, nameof(request));
if (request is Type type)
return _proxyTypes.Contains(type);
return false;
}
private readonly IEnumerable<Type> _proxyTypes;
}
因为DepClass
既不是接口也不是抽象类型,默认情况下,AutoFixture 不会依赖模拟框架来创建实例,而是使用类型的实际构造函数。
由于 NSubstitute 没有来自其他流行模拟框架的 Mock<T>
或 Fake<T>
的等价物,AutoFixture 必须提供一个特殊的样本构建器,称为 SubstituteRelay
,以填充差距。您可以像使用任何其他样本生成器一样使用此 class 来指示 Fixture 在请求特定类型实例时 return 进行模拟。
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
fixture.Customizations
.Add(new SubstituteRelay(new ExactTypeSpecification(typeof(DepClass))));
此构造有点冗长,因此您可以创建一个通用中继来缩短语法。
public class SubstituteRelay<T> : CompositeSpecimenBuilder
{
public SubstituteRelay()
: base(new SubstituteRelay(new ExactTypeSpecification(typeof(T))))
{
}
}
你应该可以编写类似这样的测试。
[Fact]
public void ReturnsExpectedValue()
{
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
fixture.Customizations.Add(new SubstituteRelay<DepClass>());
fixture.Freeze<DepClass>().Get().ReturnsForAnyArgs("1234");
var sut = fixture.Create<HostClass>();
var actual = sut.Magic();
Assert.Equal("1234", actual);
}
为了更接近您的预期 API,您可以创建自己的扩展方法,抽象出注册中继和冻结实例的两行代码。
我想用虚拟方法测试一个 class 依赖于另一个 class 的 class。
class DepClass
{
public virtual string Get() => "";
}
class HostClass
{
private _c;
public Host(DepClass c){ _c = c; }
public string Magic() => _c.Get();
}
现在我想用 Autofixture + NSubstitute 测试 HostClass
。
我的期望:
Fixture.Freeze<DepClass>().Get().ReturnsForAnyArg("123");
var sut = Fixture.Create<HostClass>();
var res = sut.Magic(); //should be 123
事实上,当我执行 Freeze().Get().Returns()
时,真正的 Get
方法被调用。
如何自定义 Autofixture 来模拟所有虚方法?
不讨论接口与虚拟方法等会很好
更新 这不起作用:
Fixture.Freeze<DepClass>().Get().ReturnsForAnyArgs("123");
同时,这个有效:
Substitute.For<DepClass>().Get().ReturnsForAnyArgs("123");
除了回答 另一种可能不符合您需求的方法:在我的例子中,我希望只有直接依赖项可以有虚拟方法。较低级别的所有依赖项都将被模拟。
因此,我决定采用更具体的方式。下面的代码是一个示例,因此您可能需要在将其用于您的解决方案之前对其进行修改。
abstract class UnitTestBase<T>
{
protected IFixture Fixture { get; private set; }
protected IList<Type> ProxyTypes {get; private set;}
protected CreateSut(): T => Fixt.Create<T>();
[SetUp]
protected virtual Setup()
{
ProxyTypes = new List<Type>();
Fixture = new Fixture().Customize(new CompositeCustomization(
new AutoNSubstituteCustomization {
ConfigureMembers = true,
GenerateDelegates = true
})
);
SetupTypesToProxy();
Fixture.Customizations.Add(new SubstituteRelay(new ProxyExactTypesSpecification(ProxyTypes)));
}
protected virtual void SetupTypesToProxy()
=> typeof(T)
.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.SelectMany(ctorInfo => ctorInfo
.GetParameters()
.Select(paramInfo => paramInfo.ParameterType)
.Where(ShouldProxy))
.ForEach(t => ProxyTypes.Add(t));
private static bool ShouldProxy(Type type)
=> !type.GetTypeInfo().IsInterface && !type.GetTypeInfo().IsAbstract;
}
internal class ProxyExactTypesSpecification: IRequestSpecification
{
public ProxyExactTypesSpecification(
IEnumerable<Type> types
)
{
_proxyTypes = types ?? Type.EmptyTypes;
}
public bool IsSatisfiedBy(
object request
)
{
Argument.NotNull(request, nameof(request));
if (request is Type type)
return _proxyTypes.Contains(type);
return false;
}
private readonly IEnumerable<Type> _proxyTypes;
}
因为DepClass
既不是接口也不是抽象类型,默认情况下,AutoFixture 不会依赖模拟框架来创建实例,而是使用类型的实际构造函数。
由于 NSubstitute 没有来自其他流行模拟框架的 Mock<T>
或 Fake<T>
的等价物,AutoFixture 必须提供一个特殊的样本构建器,称为 SubstituteRelay
,以填充差距。您可以像使用任何其他样本生成器一样使用此 class 来指示 Fixture 在请求特定类型实例时 return 进行模拟。
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
fixture.Customizations
.Add(new SubstituteRelay(new ExactTypeSpecification(typeof(DepClass))));
此构造有点冗长,因此您可以创建一个通用中继来缩短语法。
public class SubstituteRelay<T> : CompositeSpecimenBuilder
{
public SubstituteRelay()
: base(new SubstituteRelay(new ExactTypeSpecification(typeof(T))))
{
}
}
你应该可以编写类似这样的测试。
[Fact]
public void ReturnsExpectedValue()
{
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
fixture.Customizations.Add(new SubstituteRelay<DepClass>());
fixture.Freeze<DepClass>().Get().ReturnsForAnyArgs("1234");
var sut = fixture.Create<HostClass>();
var actual = sut.Magic();
Assert.Equal("1234", actual);
}
为了更接近您的预期 API,您可以创建自己的扩展方法,抽象出注册中继和冻结实例的两行代码。