c# - 用虚方法模拟一个具体的类
问题描述
我想用虚拟方法测试一个依赖于另一个类的类。
class DepClass
{
public virtual string Get() => "";
}
class HostClass
{
private _c;
public Host(DepClass c){ _c = c; }
public string Magic() => _c.Get();
}
现在我想HostClass
用 Autofixture + NSubstitute 来测试一下。我的期望:
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
,以填补空白。您可以将此类用作任何其他样本构建器,以指示夹具在请求特定类型实例时返回模拟。
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,您可以创建自己的扩展方法,抽象出注册中继和冻结实例的两行代码。
推荐阅读
- linux - 输入字符串命令以运行另一个 .sh 文件 bash 脚本
- css - 使用 li vs p 标签的显示项目之间的区别?
- node.js - 如何在 nginx 中设置子域?
- python - 我想根据多个分隔符为熊猫数据框拆分一列中的文本,并为每个分隔符创建新行
- sql - SQL Server 在 Oracle 中的 begin tran..rollback 等价物
- javascript - 如何为我的 next.js 项目导出 firebase firestore
- python - 在 Django 中运行服务器时如何设置默认 url
- css - react-big-calendar 的 Flexbox 容器破坏了 UI
- javascript - Cordova DeviceReady 未触发
- android - 即使在添加 Intent.FLAG_ACTIVITY_NEW_TASK 后 startActivity 也无法正常工作