Changing the behavior of AutoFixture auto-mocking with Moq by Mark Seemann
One of my Twitter followers who appears to be using AutoFixture recently asked me this:
So with the AutoMoqCustomization I feel like I should get Mocks with concrete types too (except the sut) - why am I wrong?
AutoFixture's extention for auto-mocking with Moq was never meant as a general modification of behavior. Customizations extend the behavior of AutoFixture; they don't change it. There's a subtle difference. In any case, the auto-mocking customization was always meant as a fallback mechanism that would create Mocks for interfaces and abstract types because AutoFixture doesn't know how to deal with those.
Apparently @ZeroBugBounce also want concrete classes to be issued as Mock instances, which is not quite the same thing; AutoFixture already has a strategy for that (it's called ConstructorInvoker).
Nevertheless I decided to spike a little on this to see if I could get it working. It turns out I needed to open some of the auto-mocking classes a bit for extensibility (always a good thing), so the following doesn't work with AutoFixture 2.0 beta 1, but will probably work with the RTW. Also please not that I'm reporting on a spike; I haven't thoroughly tested all edge cases.
That said, the first thing we need to do is to remove AutoFixture's default ConstructorInvoker that invokes the constructor of concrete classes. This is possible with this constructor overload:
public Fixture(DefaultRelays engineParts)
This takes as input a DefaultRelays instance, which is more or less just an IEnumerable<ISpecimenBuilder> (the basic building block of AutoFixture). We need to replace that with a filter that removes the ConstructorInvoker. Here's a derived class that does that:
public class FilteringRelays : DefaultEngineParts { private readonly Func<ISpecimenBuilder, bool> spec; public FilteringRelays(Func<ISpecimenBuilder, bool> specification) { if (specification == null) { throw new ArgumentNullException("specification"); } this.spec = specification; } public override IEnumerator<ISpecimenBuilder> GetEnumerator() { var enumerator = base.GetEnumerator(); while (enumerator.MoveNext()) { if (this.spec(enumerator.Current)) { yield return enumerator.Current; } } } }
DefaultEngineParts already derive from DefaultRelays, so this enables us to use the overloaded constructor to remove the ConstructorInvoker by using these filtered relays:
Func<ISpecimenBuilder, bool> concreteFilter = sb => !(sb is ConstructorInvoker); var relays = new FilteringRelays(concreteFilter);
The second thing we need to do is to tell the AutoMoqCustomization that it should Mock all types, not just interfaces and abstract classes. With the new (not in beta 1) overload of the constructor, we can now supply a Specification that determines which types should be mocked.:
Func<Type, bool> mockSpec = t => true;
We can now create the Fixture like this to get auto-mocking of all types:
var fixture = new Fixture(relays).Customize( new AutoMoqCustomization(new MockRelay(mockSpec)));
With this Fixture instance, we can now create concrete classes that are mocked. Here's the full test that proves it:
[Fact] public void CreateAnonymousMockOfConcreteType() { // Fixture setup Func<ISpecimenBuilder, bool> concreteFilter = sb => !(sb is ConstructorInvoker); var relays = new FilteringRelays(concreteFilter); Func<Type, bool> mockSpec = t => true; var fixture = new Fixture(relays).Customize( new AutoMoqCustomization(new MockRelay(mockSpec))); // Exercise system var foo = fixture.CreateAnonymous<Foo>(); foo.DoIt(); // Verify outcome var fooTD = Mock.Get(foo); fooTD.Verify(f => f.DoIt()); // Teardown }
Foo is this concrete class:
public class Foo { public virtual void DoIt() { } }
Finally, a word of caution: this is a spike. It's not fully tested and is bound to fail in certain cases: at least one case is when the type to be created is sealed. Since Moq can't create a Mock of a sealed type, the above code will fail in that case. However, we can address this issue with some more sophisticated filters and Specifications. However, I will leave that up to the interested reader (or a later blog post).
All in all I think this provides an excellent glimpse of the degree of extensibility that is built into AutoFixture 2.0's kernel.