C# 使用例如Moq和AutoFixture创建模拟对象和匿名对象的混合体?
我在工作中遇到了一个类似这样的类:C# 使用例如Moq和AutoFixture创建模拟对象和匿名对象的混合体?,c#,mocking,moq,autofixture,anonymous-objects,C#,Mocking,Moq,Autofixture,Anonymous Objects,我在工作中遇到了一个类似这样的类: public class MyObject { public int? A {get; set;} public int? B {get; set;} public int? C {get; set;} public virtual int? GetSomeValue() { //simplified behavior: return A ?? B ?? C; } } var fixture = new Fixtu
public class MyObject
{
public int? A {get; set;}
public int? B {get; set;}
public int? C {get; set;}
public virtual int? GetSomeValue()
{
//simplified behavior:
return A ?? B ?? C;
}
}
var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();
问题是,我有一些代码可以访问A、B和C,并调用GetSomeValue()方法(现在,我想说这不是一个好的设计,但有时我会束手无策;-)。我想创建这个对象的模拟,同时将a、B和C设置为一些值。因此,当我使用最小起订量时:
var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
var m=new Mock(){DefaultValue=DefaultValue.Mock};
让我在GetSomeValue()方法上设置一个结果,但所有属性都设置为null(使用setup()设置所有属性非常麻烦,因为实际对象是一个讨厌的数据对象,并且比上面简化的示例中的属性更多)
另一方面,使用AutoFixture如下所示:
public class MyObject
{
public int? A {get; set;}
public int? B {get; set;}
public int? C {get; set;}
public virtual int? GetSomeValue()
{
//simplified behavior:
return A ?? B ?? C;
}
}
var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();
var fixture=newfixture();
var anyMyObject=fixture.CreateAnonymous();
使我无法调用GetSomeValue()方法
有没有办法将两者结合起来,拥有匿名值和设置调用结果的能力
编辑
根据nemesv的回答,我推导出了以下实用方法(希望我得到了正确的答案):
publicstaticmock-AnonymousMock(),其中T:class
{
var mock=new mock();
fixture.Customize(c=>c.FromFactory(()=>mock.Object));
fixture.CreateAnonymous();
夹具。定制。移除(0);
返回模拟;
}
可能有更好的原因,但这是可行的:
var fixture = new Fixture();
var moq = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
moq.Setup(m => m.GetSomeValue()).Returns(3);
fixture.Customize<MyObject>(c => c.FromFactory(() => moq.Object));
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Assert.AreEqual(3, anyMyObject.GetSomeValue());
Assert.IsNotNull(anyMyObject.A);
//...
var fixture=newfixture();
var moq=new Mock(){DefaultValue=DefaultValue.Mock};
moq.Setup(m=>m.GetSomeValue()).Returns(3);
fixture.Customize(c=>c.FromFactory(()=>moq.Object));
var anyMyObject=fixture.CreateAnonymous();
AreEqual(3,anyMyObject.GetSomeValue());
Assert.IsNotNull(anyMyObject.A);
//...
最初我尝试使用fixture.Register(()=>moq.Object)
而不是fixture。自定义
,但它使用省略autoproperties()
注册创建者函数,因此它不适用于您的情况。这实际上可以使用AutoFixture实现,但它确实需要一些调整。扩展点都在那里,但我承认在这种情况下,解决方案不是特别容易发现
如果您想让它处理嵌套/复杂类型,它会变得更加困难
给定上面的MyObject
类,以及这个MyParent
类:
public class MyParent
{
public MyObject Object { get; set; }
public string Text { get; set; }
}
这些单元测试全部通过:
public class Scenario
{
[Fact]
public void CreateMyObject()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(actual.A);
Assert.NotNull(actual.B);
Assert.NotNull(actual.C);
}
[Fact]
public void MyObjectIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(Mock.Get(actual));
}
[Fact]
public void CreateMyParent()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(actual.Object);
Assert.NotNull(actual.Text);
Assert.NotNull(Mock.Get(actual.Object));
}
[Fact]
public void MyParentIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(Mock.Get(actual));
}
}
MockPostprocessor
、MockConstructorQuery
和MockRelay
类在to AutoFixture中定义,因此您需要添加对此库的引用。但是,请注意,不需要添加自动moqcustomization
AutoExceptMoqPropertiesCommand类也是为以下情况定制的:
public class AutoExceptMoqPropertiesCommand : AutoPropertiesCommand<object>
{
public AutoExceptMoqPropertiesCommand()
: base(new NoInterceptorsSpecification())
{
}
protected override Type GetSpecimenType(object specimen)
{
return specimen.GetType();
}
private class NoInterceptorsSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var fi = request as FieldInfo;
if (fi != null)
{
if (fi.Name == "__interceptors")
return false;
}
return true;
}
}
}
公共类AutoExceptMoqPropertiesCommand:AutoPropertiesCommand
{
公共自动例外moqPropertiesCommand()
:base(新的NoInterceptorsSpecification())
{
}
受保护的覆盖类型GetSpecimentType(对象样本)
{
返回sample.GetType();
}
私有类NoInterceptor规范:IRequestSpecification
{
公共布尔满足(对象请求)
{
var fi=请求作为FieldInfo;
如果(fi!=null)
{
如果(fi.Name==“\uu拦截器”)
返回false;
}
返回true;
}
}
}
此解决方案提供了该问题的一般解决方案。但是,它还没有经过广泛的测试,所以我很想得到反馈。从3.20.0开始,您可以使用自动配置MoqCustomization
。这将自动配置所有模拟,以便其成员的返回值由AutoFixture生成
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());
var mock = fixture.Create<Mock<MyObject>>();
Assert.NotNull(mock.Object.A);
Assert.NotNull(mock.Object.B);
Assert.NotNull(mock.Object.C);
var fixture=newfixture().Customize(新的自动配置moqcustomization());
var mock=fixture.Create();
Assert.NotNull(mock.Object.A);
Assert.NotNull(mock.Object.B);
Assert.NotNull(mock.Object.C);
这是一个很好的解决方案,特别是因为它涵盖了嵌套类型,但是,为了更一般地使用嵌套类型,有没有办法去掉部分:t==typeof(MyObject)| t==typeof(MyParent)?我想到的逻辑是这样的:“如果混合的嵌套类型是可模仿的,那么将其设置为混合类型,否则将其设置为匿名值”。是的,只需将谓词替换为更一般的值。谢谢!我尝试使用(t!=null&&!t.IsPrimitive&&(t.GetConstructors(BindingFlags.Public).Length!=0 | | t.GetConstructor(Type.EmptyTypes)!=null)的谓词,它似乎有效!你能看到这里少了什么吗?我将尝试对它进行一些测试,并尝试让您知道是否有任何结果。好吧,这可能已经足够好了-它是否正确最终取决于您希望以这种方式创建的类型。