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:
But after executing Setup
it looks like this (Prop2
is null
):
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>
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();
}