【问题标题】:Creating a hybrid of a mock and an anonymous object using e.g. Moq and AutoFixture?使用例如创建模拟对象和匿名对象的混合体起订量和自动夹具?
【发布时间】:2012-03-09 15:06:54
【问题描述】:

我在工作中遇到了这样一个类:

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;
  }  
}

问题是我有一些代码可以访问 A、B 和 C 并调用 GetSomeValue() 方法(现在,我会说这不是一个好的设计,但有时我的双手被束缚了 ;-))。我想创建这个对象的模拟,同时将 A、B 和 C 设置为一些值。所以,当我这样使用起订量时:

var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };

让我在 GetSomeValue() 方法上设置结果,但是所有属性都设置为 null(使用 Setup() 设置所有属性非常麻烦,因为真实对象是一个讨厌的数据对象并且具有更多属性比上面的简化示例)。

另一方面,像这样使用 AutoFixture:

var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();

让我无法调用 GetSomeValue() 方法。

有没有办法将两者结合起来,获得匿名值和设置调用结果的能力?

编辑

根据 nemesv 的回答,我推导出了以下实用方法(希望我做对了):

public static Mock<T> AnonymousMock<T>() where T : class
{
  var mock = new Mock<T>();
  fixture.Customize<T>(c => c.FromFactory(() => mock.Object));
  fixture.CreateAnonymous<T>();
  fixture.Customizations.RemoveAt(0);
  return mock;
}

【问题讨论】:

    标签: c# mocking moq autofixture anonymous-objects


    【解决方案1】:

    这个实际上可以用 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));
        }
    }
    

    MockHybridCustomization 中有什么?这个:

    public class MockHybridCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Customizations.Add(
                new MockPostprocessor(
                    new MethodInvoker(
                        new MockConstructorQuery())));
            fixture.Customizations.Add(
                new Postprocessor(
                    new MockRelay(t =>
                        t == typeof(MyObject) || t == typeof(MyParent)),
                    new AutoExceptMoqPropertiesCommand().Execute,
                    new AnyTypeSpecification()));
        }
    }
    

    MockPostprocessorMockConstructorQueryMockRelay 类在 AutoFixture 的 AutoMoq extension 中定义,因此您需要添加对此库的引用。但是请注意,不需要添加AutoMoqCustomization

    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;
            }
        }
    }
    

    此解决方案提供了该问题的一般解决方案。然而,它还没有经过广泛的测试,所以我很想得到关于它的反馈。

    【讨论】:

    • 这是一个很好的解决方案,特别是因为它涵盖了嵌套类型,但是,为了更通用的嵌套类型,有没有办法摆脱这部分:t == typeof(MyObject ) || t == typeof(MyParent)?我正在考虑的逻辑类似于:“如果混合的嵌套类型是可模拟的,则将其设为混合,否则将其设为匿名值”。
    • 是的,只需将谓词替换为更通用的东西即可。
    • 谢谢!我尝试使用谓词 (t != null && !t.IsPrimitive && (t.GetConstructors(BindingFlags.Public).Length != 0 || t.GetConstructor(Type.EmptyTypes) != null)) 似乎去工作!你能看到这里有什么遗漏吗?我会尝试对其进行一些测试,并尝试让您知道是否有任何结果。
    • 嗯,这可能已经足够好了——它是否正确最终取决于你想要以这种方式创建的类型。
    【解决方案2】:

    可能有更好的原因,但这是可行的:

    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);
    //...
    

    最初我尝试使用fixture.Register(() =&gt; moq.Object); 而不是fixture.Customize,但它使用OmitAutoProperties() 注册了创建者函数,因此它不适用于您的情况。

    【讨论】:

      【解决方案3】:

      从 3.20.0 开始,您可以使用 AutoConfiguredMoqCustomization。这将自动配置所有模拟,以便其成员的返回值由 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);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多