使用Moq和AutoFixture等创建mock和匿名对象的混合体

本文关键字:对象 混合体 创建 Moq AutoFixture 使用 mock | 更新日期: 2023-09-27 18:25:13

我在工作中遇到一个类,它看起来像这样:

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()方法(现在,我认为这不是一个好的设计,但有时我会束手束脚;-)。我想创建这个对象的mock,同时将a、B和C设置为一些值。所以,当我这样使用moq时:

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

使用Moq和AutoFixture等创建mock和匿名对象的混合体

这个实际上可以用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扩展中定义的,因此您需要添加对此库的引用。但是,请注意,不需要添加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;
        }
    }
}

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

可能有更好的原因,但这很有效:

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(() => moq.Object);而不是fixture.Customize,但它用OmitAutoProperties()注册了创建者函数,所以它不适用于您的情况。

从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);