Automocking and the Dependency Inversion Principle
I had reason to revisit the automocked base class from a previous blog post. I am working with another code base and have new opportunities for automocking. We have a lot of internal classes. Approximately 30% of the classes are marked as internal. The old approach did not work anymore.
With an internal subject, I got this error:
Inconsistent accessibility: base class 'WithSubject<HelloWorld>' is less accessible than class 'When_GetMessage'
The Dependency Inversion Principle
The D in SOLID stands for the Dependency Inversion Principle:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions. B. Abstractions should not depend on details. Details should depend on abstractions.
In other words:
Depend on abstractions, not on concretions.
One way to enforce this between projects in C# is to make classes internal
.
The internal access modifier
The internal
access modifier:
Internal types or members are accessible only within files in the same assembly
This is useful:
A common use of internal access is in component-based development because it enables a group of components to cooperate in a private manner without being exposed to the rest of the application code.
How can the unit tests access the internal classes then?
The [InternalsVisibleTo]
attribute:
types that are ordinarily visible only within the current assembly are visible to a specified assembly.
We will add the attribute to the AssemblyInfo file in the project under test, to make the unit test project a friend assembly.
We will use an IoC container to configure the creation of the internal classes. The clients will depend on public interfaces and the IoC container.
WithFakes
My favorite framework for testing is still Machine.Specifications in combination with Machine.Fakes for automocking support.
At work, we use:
I will mimic the Machine.Fakes WithFakes
base class:
-
The test fixture will inherit from
WithFakes
-
Use
The<TFake>()
method for creating fakes
My implementation will use:
Code
You can get the example code at https://github.com/hlaueriksson/ConductOfCode
WithFakes
using System; | |
using System.Linq.Expressions; | |
using Moq; | |
using Moq.AutoMock; | |
namespace ConductOfCode.Tests.Fakes | |
{ | |
/// <summary> | |
/// Base class that adds auto mocking/faking to Xunit. | |
/// </summary> | |
public abstract class WithFakes : IDisposable | |
{ | |
private readonly AutoMocker _mocker; | |
private object _subject; | |
protected WithFakes() | |
{ | |
_mocker = new AutoMocker(); | |
} | |
public void Dispose() | |
{ | |
_subject = null; | |
} | |
/// <summary> | |
/// Gives access to the subject under specification. | |
/// On first access the spec tries to create an instance of the subject type by itself. | |
/// </summary> | |
/// <typeparam name="TSubject">The type of subject to create.</typeparam> | |
/// <returns>The subject of the specification.</returns> | |
protected TSubject Subject<TSubject>() where TSubject : class | |
{ | |
if (_subject != null) return _subject as TSubject; | |
_subject = _mocker.CreateInstance<TSubject>(); | |
return (TSubject)_subject; | |
} | |
/// <summary> | |
/// Creates a fake of the type specified by <typeparamref name="TInterfaceType" />. | |
/// This method reuses existing instances. If an instance of <typeparamref name="TInterfaceType" /> | |
/// was already requested it's returned here. (You can say this is kind of a singleton behavior) | |
/// Besides that, you can obtain a reference to injected instances/fakes with this method. | |
/// </summary> | |
/// <typeparam name="TInterfaceType">The type to create a fake for. (Should be an interface or an abstract class)</typeparam> | |
/// <returns> | |
/// An instance implementing <typeparamref name="TInterfaceType" />. | |
/// </returns> | |
protected Mock<TInterfaceType> The<TInterfaceType>() where TInterfaceType : class | |
{ | |
return _mocker.GetMock<TInterfaceType>(); | |
} | |
/// <summary> | |
/// Configures the specification to use the specified instance for <typeparamref name="TInterfaceType" />. | |
/// </summary> | |
/// <typeparam name="TInterfaceType">The type to inject.</typeparam> | |
/// <param name="instance">The instance to inject.</param> | |
protected void With<TInterfaceType>(TInterfaceType instance) | |
{ | |
_mocker.Use(instance); | |
} | |
/// <summary> | |
/// Configures the specification to use the specified mock for <typeparamref name="TInterfaceType" />. | |
/// </summary> | |
/// <typeparam name="TInterfaceType">The type to inject.</typeparam> | |
/// <param name="mock">The mock to inject.</param> | |
protected void With<TInterfaceType>(Mock<TInterfaceType> mock) where TInterfaceType : class | |
{ | |
_mocker.Use(mock); | |
} | |
/// <summary> | |
/// Configures the specification to setup a mock with specified behavior for <typeparamref name="TInterfaceType" />. | |
/// </summary> | |
/// <typeparam name="TInterfaceType">The type to inject.</typeparam> | |
/// <param name="setup">The behavior to inject.</param> | |
protected void With<TInterfaceType>(Expression<Func<TInterfaceType, bool>> setup) where TInterfaceType : class | |
{ | |
_mocker.Use(setup); | |
} | |
} | |
} |
The Subject<TSubject>()
method gives access the class under test.
This is how the error Inconsistent accessibility: base class 'WithSubject<HelloWorld>' is less accessible than class 'When_GetMessage'
is solved.
The The<TFake>()
method gives access to the injected dependencies from the subject.
The With<TFake>()
methods can be used to inject real or fake objects into the subject.
The subject
namespace ConductOfCode.Core | |
{ | |
public interface IHelloWorld | |
{ | |
string GetMessage(); | |
} | |
internal class HelloWorld : IHelloWorld | |
{ | |
private readonly IFoo _foo; | |
private readonly IBar _bar; | |
public HelloWorld(IFoo foo, IBar bar) | |
{ | |
_foo = foo; | |
_bar = bar; | |
} | |
public string GetMessage() | |
{ | |
return _foo.GetFoo() + _bar.GetBar(); | |
} | |
} | |
public interface IFoo | |
{ | |
string GetFoo(); | |
} | |
internal class Foo : IFoo | |
{ | |
public string GetFoo() => "Hello"; | |
} | |
public interface IBar | |
{ | |
string GetBar(); | |
} | |
internal class Bar : IBar | |
{ | |
public string GetBar() => ", World!"; | |
} | |
} |
The interfaces are public
, the concrete classes are internal
.
using StructureMap; | |
namespace ConductOfCode.Core | |
{ | |
public class HelloWorldRegistry : Registry | |
{ | |
public HelloWorldRegistry() | |
{ | |
For<IHelloWorld>().Use<HelloWorld>(); | |
For<IFoo>().Use<Foo>(); | |
For<IBar>().Use<Bar>(); | |
} | |
} | |
} |
The creation of internal classes is configured with StructureMap, the IoC container we are using.
The AssemblyInfo.cs
is also modified to make the subject accessible for the unit tests:
[assembly: InternalsVisibleTo("ConductOfCode.Tests")]
.
The client
using System; | |
using ConductOfCode.Core; | |
namespace ConductOfCode | |
{ | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
IoC.Container.AssertConfigurationIsValid(); | |
var helloWorld = IoC.Container.GetInstance<IHelloWorld>(); | |
Console.WriteLine(helloWorld.GetMessage()); | |
} | |
} | |
} |
The client depends on interfaces and uses the IoC container to create concrete classes.
using System; | |
using StructureMap; | |
namespace ConductOfCode | |
{ | |
public static class IoC | |
{ | |
private static readonly Lazy<Container> Lazy = new Lazy<Container>(CreateContainer); | |
public static IContainer Container => Lazy.Value; | |
private static Container CreateContainer() | |
{ | |
return new Container(_ => | |
_.Scan(x => | |
{ | |
x.AssembliesAndExecutablesFromApplicationBaseDirectory(); | |
x.LookForRegistries(); | |
}) | |
); | |
} | |
} | |
} |
The IoC container scans the assemblies for registries with configuration.
The tests
using ConductOfCode.Core; | |
using ConductOfCode.Tests.Fakes; | |
using Moq; | |
using Should; | |
using Xunit; | |
namespace ConductOfCode.Tests.Given_HelloWorld | |
{ | |
public class When_GetMessage : WithFakes | |
{ | |
private HelloWorld Subject => Subject<HelloWorld>(); | |
public When_GetMessage() | |
{ | |
The<IFoo>().Setup(x => x.GetFoo()).Returns("Hello"); | |
With<IBar>(x => x.GetBar() == ", World!"); | |
} | |
[Fact] | |
public void Should_invoke_IFoo_GetMessage() | |
{ | |
Subject.GetMessage(); | |
The<IFoo>().Verify(x => x.GetFoo()); | |
} | |
[Fact] | |
public void Should_invoke_IBar_GetMessage() | |
{ | |
Subject.GetMessage(); | |
The<IBar>().Verify(x => x.GetBar(), Times.Once); | |
} | |
[Fact] | |
public void Should_return_a_concatenated_string_with_messages_from_IFoo_and_IBar() | |
{ | |
Subject.GetMessage().ShouldEqual("Hello, World!"); | |
} | |
} | |
} |
The Subject
property gives access to the automocked instance via the Subject<TSubject>()
method from the base class.
The With<TFake>()
methods can be used to inject and setup mocks.
The The<TFake>()
method is used for setup and verification of mocks.
The unit test sessions