Why does Autofixture w/ AutoMoqCustomization stop complaining about lack of parameterless constructor when class is sealed?

£可爱£侵袭症+ 提交于 2019-11-30 19:19:24

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!