When I use Moq directly to mock IBuilderFactory
and instantiate BuilderService
myself in a unit test, I can get a passing test w
If you look at the stack trace, you'll notice that the exception happens deep within Moq. AutoFixture is an opinionated library, and one of the opinions it holds is that nulls are invalid return values. For that reason, AutoMoqCustomization configures all Mock instances like this:
mock.DefaultValue = DefaultValue.Mock;
(among other things). Thus, you can reproduce the failing test entirely 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());
}
The strange thing is that it still seems to work with sealed classes. This is, however, not quite true, but rather originates in the OP tests being False Negatives.
Consider this Characterization Test of Moq:
[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 it's been asked to create an instance of CubeBuilder, and it doesn't know how to do that, because CubeBuilder has no default constructor, and no Setup
tells it how to deal with calls to Create
.
(As an aside, the irony here is that AutoFixture would be perfectly able to create an instance of CubeBuilder, but there's no extensibility point in Moq that enables AutoFixture to go in and take over Moq's default object instance creation behaviour.)
Now consider this Characterization test when a 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 having been implicitly told not to return null
, Moq does so anyway.
My theory is that what's really going on is that in MoqCharacterizationForUnsealedClass above, what factory.DefaultValue = DefaultValue.Mock;
really means is that Moq creates a mock of CubeBuilder - in other words, it dynamically emits a class that derives from CubeBuilder. However, when asked to create a mock of SealedCubeBuilder, it can't, because it can't create a class derived from a sealed class.
Instead of throwing an exception, it returns null
. This is inconsistent behaviour, and I've reported this as a bug in Moq.