Why does Autofixture w / AutoMoqCustomization stop complaining about the lack of a constructor without parameters when closing a class?

When I use Moq directly for the mock IBuilderFactory and create the BuilderService instance myself in the unit test, I can get a pass test that checks that the Create() IBuilderFactory is called exactly once.

However, when I use Autofixture with AutoMoqCustomization , freezing the IBuilderFactory layout and creating the BuilderService with fixture.Create<BuilderService> , I get the following exception:

System.ArgumentException: Unable to instantiate class: OddBehaviorTests.CubeBuilder. Could not find constructor without parameters. Parameter Name: constructorArguments

If I make CubeBuilder sealed (represented by replacing it with the SealedCubeBuilder class, which is called IBuilderFactoryForSealedBuilder.Create() ), the test passes using AutoFixture with AutoMoqCustomization, without exception.

Am I missing something? Since I get tests using Moq directly, I believe this is due to Autofixture and / or AutoMoqCustomization. Is this the desired behavior? If so, why?

To reproduce, I use:

 using Moq; using Ploeh.AutoFixture; using Ploeh.AutoFixture.AutoMoq; using Xunit; 

Here are four tests that illustrate behavior:

 public class BuilderServiceTests { [Fact] public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() { var factory = new Mock<IBuilderFactory>(); var sut = new BuilderService(factory.Object); sut.Create(); factory.Verify(f => f.Create(), Times.Once()); } [Fact] public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() { var fixture = new Fixture().Customize(new AutoMoqCustomization()); var factory = fixture.Freeze<Mock<IBuilderFactory>>(); var sut = fixture.Create<BuilderService>(); sut.Create(); // EXCEPTION THROWN!! factory.Verify(f => f.Create(), Times.Once()); } [Fact] public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() { var factory = new Mock<IBuilderFactoryForSealedBuilder>(); var sut = new BuilderServiceForSealedBuilder(factory.Object); sut.Create(); factory.Verify(f => f.Create(), Times.Once()); } [Fact] public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() { var fixture = new Fixture().Customize(new AutoMoqCustomization()); var factory = fixture.Freeze<Mock<IBuilderFactoryForSealedBuilder>>(); var sut = fixture.Create<BuilderServiceForSealedBuilder>(); sut.Create(); factory.Verify(f => f.Create(), Times.Once()); } } 

Here are the required classes:

 public interface IBuilderService { IBuilder Create(); } public class BuilderService : IBuilderService { private readonly IBuilderFactory _factory; public BuilderService(IBuilderFactory factory) { _factory = factory; } public IBuilder Create() { return _factory.Create(); } } public class BuilderServiceForSealedBuilder : IBuilderService { private readonly IBuilderFactoryForSealedBuilder _factory; public BuilderServiceForSealedBuilder(IBuilderFactoryForSealedBuilder factory) { _factory = factory; } public IBuilder Create() { return _factory.Create(); } } public interface IBuilderFactoryForSealedBuilder { SealedCubeBuilder Create(); } public interface IBuilderFactory { CubeBuilder Create(); } public interface IBuilder { void Build(); } public abstract class Builder : IBuilder { public void Build() { } // build stuff } public class CubeBuilder : Builder { private Cube _cube; public CubeBuilder(Cube cube) { _cube = cube; } } public sealed class SealedCubeBuilder : Builder { private Cube _cube; public SealedCubeBuilder(Cube cube) { _cube = cube; } } public class Cube { } 
+8
dependency-injection moq autofixture castle-dynamicproxy automocking
source share
1 answer

If you look at the stack trace, you will notice that the exception occurs deep inside Moq. AutoFixture is a self-confident library, and one of its opinions is that nulls are invalid return values . For this reason, AutoMoqCustomization configures all Mock instances as follows:

 mock.DefaultValue = DefaultValue.Mock; 

(by the way). Thus, you can fully reproduce the failed test without AutoFixture:

 [Fact] public void ReproWithoutAutoFixture() { var factory = new Mock<IBuilderFactory>(); factory.DefaultValue = DefaultValue.Mock; var sut = new BuilderService(factory.Object); sut.Create(); // EXCEPTION THROWN!! factory.Verify(f => f.Create(), Times.Once()); } 

It is strange that it still works with private classes. This, however, is not entirely true, but rather occurs in the OP tests, which are False Negatives .

Consider this Moq characterization test :

 [Fact] public void MoqCharacterizationForUnsealedClass() { var factory = new Mock<IBuilderFactory>(); factory.DefaultValue = DefaultValue.Mock; Assert.Throws<ArgumentException>(() => factory.Object.Create()); } 

Moq correctly throws an exception because he was asked to create an instance of CubeBuilder, and he does not know how to do it, because CubeBuilder does not have a default constructor, and no Setup tells him how to handle Create calls.

(In isolation, the irony is that AutoFixture may well create an instance of CubeBuilder, but Moq does not have an extensibility point that allows AutoFixture to enter and handle the behavior of an instance of the default Moq object.)

Now consider this feature test when the return type is sealed:

 [Fact] public void MoqCharacterizationForSealedClass() { var factory = new Mock<IBuilderFactoryForSealedBuilder>(); factory.DefaultValue = DefaultValue.Mock; var actual = factory.Object.Create(); Assert.Null(actual); } 

It turns out that in this case, despite the fact that it was implicitly told not to return null , Moq still does.

My theory is that what is actually happening is that in MoqCharacterizationForUnsealedClass above that factory.DefaultValue = DefaultValue.Mock; really means that Moq creates a CubeBuilder layout - in other words, it dynamically emits a class that derives from CubeBuilder. However, when asked to create a SealedCubeBuilder layout, it cannot, because it cannot create a class derived from a private class.

Instead of throwing an exception, it returns null . This is inconsistent behavior, and I reported it as a bug in Moq .

+11
source share

All Articles