dependency injection - Why does Autofixture w/ AutoMoqCustomization stop complaining about lack of parameterless constructor when class is sealed? -
when use moq directly mock ibuilderfactory
, instantiate builderservice
myself in unit test, can passing test verifies create()
method of ibuilderfactory
called once.
however, when use autofixture automoqcustomization, freezing mock of ibuilderfactory
, instantiating builderservice
fixture.create<builderservice>
, following exception:
system.argumentexception: can not instantiate proxy of class: oddbehaviortests.cubebuilder. not find parameterless constructor. parameter name: constructorarguments
if make cubebuilder
sealed (represented replacing sealed class sealedcubebuilder
called ibuilderfactoryforsealedbuilder.create()
, test passes using autofixture automoqcustomization, no exception thrown.
am missing something? since passing tests using moq directly, believe related autofixture and/or automoqcustomization. desired behavior? if so, why?
to reproduce, i'm using:
using moq; using ploeh.autofixture; using ploeh.autofixture.automoq; using xunit;
here 4 tests illustrating 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 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 { }
if @ stack trace, you'll notice exception happens deep within moq. autofixture opinionated library, , 1 of opinions holds nulls invalid return values. reason, automoqcustomization configures mock instances this:
mock.defaultvalue = defaultvalue.mock;
(among other things). thus, can reproduce 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 still seems work sealed classes. is, however, not quite true, rather originates in op tests being false negatives.
consider 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 exception because it's been asked create instance of cubebuilder, , doesn't know how that, because cubebuilder has no default constructor, , no setup
tells how deal calls create
.
(as aside, irony here autofixture able create instance of cubebuilder, there's no extensibility point in moq enables autofixture go in , take on moq's default object instance creation behaviour.)
now consider characterization test when return type 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 in case, despite having been implicitly told not return null
, moq anyway.
my theory what's going on in moqcharacterizationforunsealedclass above, factory.defaultvalue = defaultvalue.mock;
means moq creates mock of cubebuilder - in other words, dynamically emits class derives cubebuilder. however, when asked create mock of sealedcubebuilder, can't, because can't create class derived sealed class.
instead of throwing exception, returns null
. inconsistent behaviour, , i've reported bug in moq.
Comments
Post a Comment