Skip to content

Latest commit

 

History

History
252 lines (191 loc) · 8.46 KB

UsingInTestsWithMockingLibrary.md

File metadata and controls

252 lines (191 loc) · 8.46 KB

Auto-mocking with a mocking library

For examples below we need to add:

usings ...
namespace DryIoc.Docs;
using System;
using System.Collections.Concurrent;
using NUnit.Framework;
using DryIoc;
using NSubstitute;
using Moq;
using static DryIoc.ImTools.ArrayTools;

Auto-mocking with NSubstitute

Auto-mocking here means that we can setup container to return mocks for unregistered services. In example below we will use NSubstitute library for creating mocks, but you can use something else, e.g. Fake-it-easy or Moq.

Let's define some interfaces and classes:

public interface INotImplementedService { }

public class SomeConsumer
{
    public INotImplementedService Service { get; }
    public SomeConsumer(INotImplementedService service) { Service = service; }
}

Let's test a SomeConsumer by mocking its Service dependency:

public class NSubstitute_example
{
    readonly ConcurrentDictionary<System.Type, DynamicRegistration> _mockRegistrations =
        new ConcurrentDictionary<System.Type, DynamicRegistration>();

    // Let's define a method to configure our container with auto-mocking of interfaces or abstract classes.
    // Optional `reuse` parameter will allow to specify a mock reuse.
    private IContainer WithAutoMocking(IContainer container, IReuse reuse = null) =>
        container.With(rules => rules.WithDynamicRegistration((serviceType, serviceKey) =>
        {
            if (!serviceType.IsAbstract) // Mock interface or abstract class only.
                return null;

            if (serviceType.IsGenericTypeDefinition)
                return null; // we cannot mock open-generic types - we need something concrete here

            var d = _mockRegistrations.GetOrAdd(serviceType,
                type => new DynamicRegistration(
                    DelegateFactory.Of(r => Substitute.For(new[] { serviceType }, Empty<object>()), reuse)));

            return new[] { d };
        },
        DynamicRegistrationFlags.Service | DynamicRegistrationFlags.AsFallback));

    [Test]
    public void Example()
    {
        var container = WithAutoMocking(new Container());

        container.Register<SomeConsumer>();

        var consumer = container.Resolve<SomeConsumer>();

        Assert.IsInstanceOf<INotImplementedService>(consumer.Service);
    }
}

With above example there is still the problem though - the factory will be created each time when non-registered service is requested. It may be also problematic if we want the mock to be a singleton. Let's fix it by caching the factory in the dictionary:

public class NSubstitute_example_with_singleton_mocks
{
    readonly ConcurrentDictionary<System.Type, DynamicRegistration> _mockRegistrations =
        new ConcurrentDictionary<System.Type, DynamicRegistration>();

    // Let's define a method to configure our container with auto-mocking of interfaces or abstract classes.
    // Optional `reuse` parameter will allow to specify a mock reuse.
    private IContainer WithAutoMocking(IContainer container, IReuse reuse = null) =>
        container.With(rules => rules.WithDynamicRegistration((serviceType, serviceKey) =>
        {
            if (!serviceType.IsAbstract) // Mock interface or abstract class only.
                return null;

            if (serviceType.IsGenericTypeDefinition)
                return null; // we cannot mock open-generic types - we need something concrete here

            var d = _mockRegistrations.GetOrAdd(serviceType,
                type => new DynamicRegistration(
                    DelegateFactory.Of(r => Substitute.For(new[] { serviceType }, Empty<object>()), reuse)));

            return new[] { d };
        },
        DynamicRegistrationFlags.Service | DynamicRegistrationFlags.AsFallback));

    [Test]
    public void Example()
    {
        var container = WithAutoMocking(new Container(), Reuse.Singleton);

        container.Register<SomeConsumer>();
        container.Register<OtherConsumer>();

        var consumer1 = container.Resolve<SomeConsumer>();
        var consumer2 = container.Resolve<OtherConsumer>();

        // Verify that `Service` dependency is indeed a singleton in a different consumers
        Assert.AreSame(consumer1.Service, consumer2.Service);
    }

    public class OtherConsumer
    {
        public INotImplementedService Service { get; }
        public OtherConsumer(INotImplementedService service) { Service = service; }
    }

    [Test]
    public void Example_of_mocking_the_open_generic_dependency()
    {
        var container = WithAutoMocking(new Container(), Reuse.Singleton);

        container.Register<Foo>();

        var consumer = container.Resolve<Foo>();
    }

    public interface IOpenGenericDependency<T> { }
    public class Foo
    {
        public readonly IOpenGenericDependency<int> Dependency;
        public Foo(IOpenGenericDependency<int> dependency) => Dependency = dependency;
    }
}

Auto-mocking with Moq

Let's implement auto-mocking with popular Moq library.

We will create a testing container which dynamically provides the mock implementation for any interface or abstract class and automatically will resolve any concrete class. We will use the DynamicRegistrationProvider feature for those and CreateChild container to detach from the production container but provide the access to its services.

public class Moq_example_with_test_container
{
    [Test]
    public void Example()
    {
        var prodContainer = new Container();

        using (var container = CreateTestContainer(prodContainer))
        {
            container.Register<UnitOfWork>(Reuse.Singleton);

            // Arrangements
            const bool expected = true;

            container.Resolve<Mock<IDep>>()
                .Setup(instance => instance.Method())
                .Returns(expected);

            // Get concrete type instance of tested unit 
            // all dependencies are fulfilled with mocked instances
            var unit = container.Resolve<UnitOfWork>();

            // Action
            var actual = unit.InvokeDep();

            // Assertion
            Assert.AreEqual(expected, actual);
            container.Resolve<Mock<IDep>>()
                .Verify(instance => instance.Method());
        }
    }

    public static IContainer CreateTestContainer(IContainer container)
    {
        var c = container.CreateChild(IfAlreadyRegistered.Replace,
            container.Rules.WithDynamicRegistration((serviceType, serviceKey) =>
            {
                // ignore services with non-default key
                if (serviceKey != null)
                    return null;

                if (serviceType == typeof(object))
                    return null;

                // get the Mock object for the abstract class or interface
                if (serviceType.IsInterface || serviceType.IsAbstract)
                {
                    // except for the open-generic ones
                    if (serviceType.IsGenericType && serviceType.IsOpenGeneric())
                        return null;

                    var mockType = typeof(Mock<>).MakeGenericType(serviceType);

                    var mockFactory = DelegateFactory.Of(r => ((Mock)r.Resolve(mockType)).Object, Reuse.Singleton);

                    return new[] { new DynamicRegistration(mockFactory, IfAlreadyRegistered.Keep) };
                }

                // concrete types
                var concreteTypeFactory = serviceType.ToFactory(Reuse.Singleton, FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic);

                return new[] { new DynamicRegistration(concreteTypeFactory) };
            },
            DynamicRegistrationFlags.Service | DynamicRegistrationFlags.AsFallback));

        c.Register(typeof(Mock<>), Reuse.Singleton, FactoryMethod.DefaultConstructor());

        return c;
    }

    public interface IDep
    {
        bool Method();
    }

    public class Dep1 : IDep
    {
        public bool Method() => true;
    }

    public class UnitOfWork : IDisposable
    {
        public readonly IDep Dep;
        public UnitOfWork(IDep d) => Dep = d;
        public void Dispose() { }

        public bool InvokeDep() => Dep.Method();
    }
}