About causing confusion at using AutoMocking(with moq)

Nov 22, 2011 at 9:57 PM
Edited Nov 22, 2011 at 10:14 PM

There are IFoo and IBar interfaces having the same properties.
Like Test1 and Test2 unit tests, FooProp and BarProp return NULL when I try to "Freeze()" with the each interface
However, with the both like Test3, BarProp does not return NULL. 
It acts like "_Fixture.Freeze<Mock<IBar>>()" or "_Fixture.CreateAnonymous<Mock<IBar>>()".

I do not know whether this case is intended or not. If this is intended, I really want to know the reason.

#region Test Classes

public interface IFoo
{
	int IntValue { get; set; }
	TestClass FooProp { get; set; }
}
public interface IBar
{
	int IntValue { get; set; }
	TestClass BarProp { get; set; }
}
public class TestClass { }

#endregion

[TestClass]
public class UnitTest
{
	private IFixture _Fixture;
	[TestInitialize]
	public void TestInitialize()
	{
		_Fixture = new Fixture().Customize(new AutoMoqCustomization());
	}

	[TestMethod]
	public void Test1()
	{
		//Act
		var mockFoo = _Fixture.Freeze<Mock<IFoo>>(
			c => c.Do(m => m.SetupProperty(x => x.IntValue, 100))
			);
		//Assert
		Assert.IsNull(mockFoo.Object.FooProp);
	}

	[TestMethod]
	public void Test2()
	{
		//Act
		var mockBar = _Fixture.Freeze<Mock<IBar>>(
				c => c.Do(m => m.SetupProperty(x => x.IntValue, 100))
		);
		//Assert
		Assert.IsNull(mockBar.Object.BarProp);
	}

	[TestMethod]
	public void Test3()
	{
		//Act
		var mockFoo = _Fixture.Freeze<Mock<IFoo>>(
			c => c.Do(m => m.SetupProperty(x => x.IntValue, 100))
			);
		var mockBar = _Fixture.Freeze<Mock<IBar>>(
				c => c.Do(m => m.SetupProperty(x => x.IntValue, 100))
		);
		//Assert
		Assert.IsNull(mockFoo.Object.FooProp);
		Assert.IsNull(mockBar.Object.BarProp);//Fail!!!
	}
}
Nov 23, 2011 at 2:17 AM
Edited Nov 23, 2011 at 5:45 AM

I would like to post another case to be simpler.

[TestClass]
public class UnitTest2_SimplerCase
{
	[TestMethod]
	public void TestMethod1()
	{
		var fixture = new Fixture().Customize(new AutoMoqCustomization());

		var instance1 = fixture.CreateAnonymous<Mock<IFoo>>();
		Assert.IsNotNull(instance1.Object.Bar);//Passed => OK
		var instance2 = fixture.Build<Mock<IFoo>>().CreateAnonymous();
		Assert.IsNull(instance2.Object.Bar);//Passed => OK
		var instance3 = fixture.Build<Mock<IFoo>>().CreateAnonymous();
		Assert.IsNull(instance3.Object.Bar);//Not Failed => ???
	}

	public interface IFoo
	{
		IBar Bar { get; set; }
	}

	public interface IBar
	{
	}
}

Coordinator
Nov 23, 2011 at 12:50 PM

Thanks for reporting this issue and for providing an easy repro.

This looks really strange at first glance. It's definitely a big POLA violation, and I had to do a double take before I understood what was going on. However, it turns out that this is actually not a bug... It's probably a stretch to say that it's by design (it's not), but basically, AutoFixture does what it's designed to do. Here's what's going on.

When you use the Build method, you basically kick off a one-off customization which is only scoped within that particular method chain. All customizations (e.g. the AutoMoqCustomization) are left behind and are not part of the Build method chain. Now, that is by design because it gives a ton of other POLA violations if it didn't do that.

In any case, with the AutoMoqCustomization out of the picture, AutoFixture's default object creation algorithm takes over. Since AutoProperties are turned on by default, the Mock.DefaultValue property is being assigned a value, and since the default algorithm for enums is to round-robin over all the possible values, every other time you create an instance, DefaultValue will be set to Mock and the Bar property will be implemented (by Moq) so that it returns a new Mock instance.

The fix is actually pretty simple: invoke OmitAutoProperties as part of your Build method chain - this is often a good idea, so you can consider it a 'best practice'. This modified test passes:

var fixture = new Fixture().Customize(new AutoMoqCustomization());

var instance1 = fixture.CreateAnonymous<Mock<IFoo>>();
Assert.NotNull(instance1.Object.Bar);//Passed => OK
var instance2 = fixture.Build<Mock<IFoo>>().OmitAutoProperties().CreateAnonymous();
Assert.Null(instance2.Object.Bar);//Passed => OK
var instance3 = fixture.Build<Mock<IFoo>>().OmitAutoProperties().CreateAnonymous();
Assert.Null(instance3.Object.Bar);//Passed => OK

Nov 23, 2011 at 6:13 PM
Edited Nov 24, 2011 at 4:10 AM

First, I would like to thank you for sharing the great unit testing framework.

I got the solution of the above case, but let me tell something more about causing confusion.

Actually, at the simpler case above, it seems to be more reasonable that "fixture.Build<Mock<IFoo>>().CreateAnonymous().Bar IS NOT NULL", because it is expected that the behavior of "Fixture.CreateAnonymous()" is the same as"Fixture.Build().CreateAnonymous()" or "Fixture.Freeze()" in the way of constructing a anonymous object. As you mentioned, in end user side, it will be felt to be strange, but is actually POLA violation. As I will take long time to understand POLA violation situations with AutoFixture source code, I would like to tell something in end users side.

There are the two case of some POLAs, but I do not sure whether each one is valuable or not. I would like to post a wish in a way of end users and a workaround of it.

 

[Edited]  Especially at the CASE2, if the workaround I wrote is the only solution, setting up the mock explicitly, and explicit mocking is your intention, it would be consistent that Fixture.CreateAnonymous<IFoo>().Bar (also Freeze<IFoo>.Bar) basically returns NULL. [/Edited].

 

 

public interface IFoo
{
	IBar Bar { get; set; }
	IBar Bar2 { get; set; }
}

public interface IBar
{
}

//CASE1: It is the contrary case of the previous case.
[TestMethod]
public void Wish1()
{
	var fixture = new Fixture().Customize(new AutoMoqCustomization());
	
	var instance1 = fixture.CreateAnonymous<Mock<IFoo>>();
	Assert.IsNotNull(instance1.Object.Bar);//Passed => OK

	var instance2 = fixture.Build<Mock<IFoo>>().CreateAnonymous();
	Assert.IsNotNull(instance2.Object.Bar);//Failed => ???
}
[TestMethod]
public void Workaround1()
{
	var fixture = new Fixture().Customize(new AutoMoqCustomization());

	var instance1 = fixture.CreateAnonymous<Mock<IFoo>>();
	Assert.IsNotNull(instance1.Object.Bar);//Passed => OK

	var instance2 = fixture.Build<Mock<IFoo>>()
		.FromFactory(() => fixture.CreateAnonymous<Mock<IFoo>>())
		.OmitAutoProperties().CreateAnonymous();
	Assert.IsNotNull(instance2.Object.Bar);//Passed => OK
}

//CASE2: If a Mock<Interface> is freezed, the Mock.Object(Castle) needs to be guaranteed for exposing the same object(Castle)
//       as interface members.
[TestMethod]
public void Wish2()
{
	var fixture = new Fixture().Customize(new AutoMoqCustomization());

	var instance1 = fixture.Freeze<Mock<IBar>>();
	var instance2 = fixture.Freeze<Mock<IFoo>>();
	Assert.IsNotNull(instance2.Object.Bar);//Check for Bar property not null
	Assert.IsNotNull(instance2.Object.Bar2);//Check for Bar2 property not null

	Assert.AreSame(instance1.Object, instance2.Object.Bar);//Failed => ???
}
[TestMethod]
public void Workaround2()
{
	var fixture = new Fixture().Customize(new AutoMoqCustomization());

	var instance1 = fixture.Freeze<Mock<IBar>>();
	var instance2 = fixture.Freeze<Mock<IFoo>>(c =>
		c.FromFactory(() => fixture.CreateAnonymous<Mock<IFoo>>())
		.OmitAutoProperties()
		.Do(m=>m.SetupProperty(x=>x.Bar, fixture.CreateAnonymous<IBar>()))
	);
	Assert.IsNotNull(instance2.Object.Bar);//Check for Bar property not null
	Assert.IsNotNull(instance2.Object.Bar2);//Check for Bar2 property not null

	Assert.AreSame(instance1.Object, instance2.Object.Bar);//Passed => OK
}
Coordinator
Nov 24, 2011 at 8:27 AM

Two different things are going on here, and none of them are easily 'fixed'. I'll keep them both in mind for version 3.0 of AutoFixture (which is on the drawing board), but as far as I can tell right now, addressing them will introduce breaking changes. Here's some more explanation:

1. The first case is an example of the behavior I explained earlier, and while I accept that it's a POLA violation, I'm having a hard time figuring out how to fix it.

The issue is this: the core AutoFixture library knows nothing about Moq. This is a very deliberate design decision because anyone should be able to pick up AutoFixture without feeling that a certain framework (like Moq) is being forced upon them.

Thus, all the knowledge about Moq is encapsulated in the AutoMoqCustomization class. If there's an issue with how that extension treats various Moq classes, it can be addressed there.

However, when you invoke the Build method, you deliberately leave behind all the Customizations in order to assert fine-grained control over the specific specimen you want to create. You're basically saying that you don't want to rely on the algorithms encapsulated in the Fixture instance (including the customizations), but instead you want to take control yourself. The Build method is there in order to give you (close to) total control when you need it, but in most cases, you shouldn't need it.

All this means is that all of AutoFixture's understanding about Moq is lost within the Build method chain. What is required in order to arrive at the behavior you would like is to either turn off auto-properties with the OmitAutoProperties method, or add special treatment for the DefaultValue enumeration. However, since the DefaultValue enumeration is a Moq-specific type, this special treatment can't be build into the core of AutoFixture. It could be implemented in the extension, but this is lost when you invoke the Build method.

Consider using the Build method less - personally, I hardly ever use it. What's your reason for using the Build method in this scenario?

2. This is a completely different issue which is discussed here: http://autofixture.codeplex.com/workitem/4245 Basically, AutoMoqCustomization doesn't use AutoProperties.

Nov 24, 2011 at 12:11 PM
Edited Nov 24, 2011 at 1:10 PM

There are not any specific reasons for using the Build method but the reason is to show the case confusing me, and I also do not use the Build method often. 

Thanks for your explanation.