Creating a hybrid of a mock and an anonymous object using e.g. Moq and AutoFixture?
Asked Answered
M

3

6

I encountered a class during my work that looks like this:

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

The issue is that I have some code that accesses A, B and C and calls the GetSomeValue() method (now, I'd say this is not a good design, but sometimes my hands are tied ;-)). I want to create a mock of this object, which, at the same time, has A, B and C set to some values. So, when I use moq as such:

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

lets me setup a result on GetSomeValue() method, but all the properties are set to null (and setting up all of them using Setup() is quite cumbersome, since the real object is a nasty data object and has more properties than in above simplified example).

So on the other hand, using AutoFixture like this:

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

Leaves me without the ability to stup a call to GetSomeValue() method.

Is there any way to combine the two, to have anonymous values and the ability to setup call results?

Edit

Based on nemesv's answer, I derived the following utility method (hope I got it right):

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;
}
Malatya answered 18/2, 2012 at 13:3 Comment(0)
F
5

This is actually possible to do with AutoFixture, but it does require a bit of tweaking. The extensibility points are all there, but I admit that in this case, the solution isn't particularly discoverable.

It becomes even harder if you want it to work with nested/complex types.

Given the MyObject class above, as well as this MyParent class:

public class MyParent
{
    public MyObject Object { get; set; }

    public string Text { get; set; }
}

these unit tests all pass:

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

What's in MockHybridCustomization? This:

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

The MockPostprocessor, MockConstructorQuery and MockRelay classes are defined in the AutoMoq extension to AutoFixture, so you'll need to add a reference to this library. However, note that it's not required to add the AutoMoqCustomization.

The AutoExceptMoqPropertiesCommand class is also custom-built for the occasion:

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

This solution provides a general solution to the question. However, it hasn't been extensively tested, so I'd love to get feedback on it.

Fingertip answered 19/2, 2012 at 4:29 Comment(4)
This is a nice solution, particularly because it covers nested types, however, for it to be more general with nested types, is there any way to get rid of the part: t == typeof(MyObject) || t == typeof(MyParent)? The logic I'm thinking of is something like: "if the hybrid's nested type is mockable, make it a hybrid, else make it an anonymous value".Malatya
Yes, just replace the predicate with something more general.Fingertip
Thanks! I tried to use a predicate of (t != null && !t.IsPrimitive && (t.GetConstructors(BindingFlags.Public).Length != 0 || t.GetConstructor(Type.EmptyTypes) != null)) and it seems to work! Can you see anything missing here? I'll try to give it a little testing and try to let you know if anything comes out.Malatya
Well, that's probably good enough - whether or not it's correct ultimately depends on exactly which types you want to create in this way.Fingertip
A
4

Probably there is a better why, but this works:

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

Initially I tried to use fixture.Register(() => moq.Object); instead of fixture.Customize but it registers the creator function with OmitAutoProperties() so it wouldn't work for you case.

Agnesagnese answered 18/2, 2012 at 14:10 Comment(0)
A
2

As of 3.20.0, you can use AutoConfiguredMoqCustomization. This will automatically configure all mocks so that their members' return values are generated by 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);
Aye answered 21/8, 2014 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.