Search code examples
c#unit-testingmoqautofixtureautomoq

Moq + Autofixture: Using Setup for a dependant property clears the entire mock object


I am using Moq and AutoFixture.

Given the following interfaces:

public interface Int1
{
    Int2 Int2 { get; }
}

public interface Int2
{
    string Prop1 { get; }
    string Prop2 { get; }
}

I am performing tests like these:

using AutoFixture;
using AutoFixture.AutoMoq;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
public class TestClass
{
    [TestMethod]
    public void Test1()
    {
        var f = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        obj.Object.Int2.Prop1.Should().NotBeNullOrEmpty();
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }

    [TestMethod]
    public void Test2()
    {
        var f = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        obj.Setup(q => q.Int2.Prop1).Returns("test");

        obj.Object.Int2.Prop1.Should().Be("test");
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }
}

The first test passes while the second test fails: Expected obj.Object.Int2.Prop2 not to be <null> or empty, but found <null>. It seems that when using Setup on one of the dependent properties of Int2 it clears the entire Int2 object (sets all properties to a default values). Why is that? How to avoid it?

obj.Object after creating looks like this:

Before Setup

But after executing Setup it looks like this (Prop2 is null):

After Setup

The funny thing is that when I access Int2 property after it is created, it works fine. So this test passes (variable int2 is not used anywhere):

    [TestMethod]
    public void Test2()
    {
        var f = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        var int2 = obj.Object.Int2;

        obj.Setup(q => q.Int2.Prop1).Returns("test");

        obj.Object.Int2.Prop1.Should().Be("test");
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }

Any ideas?

This is also a .csproj file for reference:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="AutoFixture" Version="4.15.0" />
        <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
        <PackageReference Include="FluentAssertions" Version="5.10.3" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
        <PackageReference Include="Moq" Version="4.16.1" />
        <PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
        <PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
    </ItemGroup>
</Project>

Solution

  • To put it simply, you end up with a different mock instance for the Int2 property, after you set the return value for the Prop1 property. To fulfill your request Moq will generate a new mock, that will return the expected value for Prop1.

    You can look at it as being equivalent to the following test:

    [Fact]
    public void Test3()
    {
        var obj2 = new Mock<IInterfaceB>();
        obj2.Setup(x => x.Property1).Returns(Guid.NewGuid().ToString());
        obj2.Setup(x => x.Property2).Returns(Guid.NewGuid().ToString());
    
        var obj = new Mock<IInterfaceA>();
        obj.Setup(x => x.PropertyB).Returns(obj2.Object);
    
        obj.Setup(q => q.PropertyB.Property1).Returns("test");
    
        Assert.Equal("test", obj.Object.PropertyB.Property1);
        Assert.NotEmpty(obj.Object.PropertyB.Property2);
    }
    

    If you want to keep your initial mock instance and just alter Prop1 then you can use Mock.Get<T>(T).

    [Fact]
    public void Test4()
    {
        var f = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
    
        var obj = f.Create<Mock<IInterfaceA>>();
    
        var obj2 = Mock.Get(obj.Object.PropertyB);
        obj2.Setup(q => q.Property1).Returns("test");
    
        Assert.Equal("test", obj.Object.PropertyB.Property1);
        Assert.NotEmpty(obj.Object.PropertyB.Property2);
    }
    

    But I will recommend using AutoFixture's Freeze feature

    [TestMethod]
    public void Test5()
    {
        var fixture = new Fixture()
            .Customize(new AutoMoqCustomization { ConfigureMembers = true });
    
        var int2Mock = fixture.Freeze<Mock<Int2>>();
        var int1Mock = fixture.Create<Mock<Int1>>();
    
        int2Mock.Setup(q => q.Prop1).Returns("test");
    
        int1Mock.Object.Int2.Prop1.Should().Be("test");
        int1Mock.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }