Automocking with LightInject plus Nsubstitute, how?
Asked Answered
C

2

5

I am new to both libraries and before committing to their usage on a large project I need clarification on my options for low-code effort automocking in my unit tests.

After spending some time on Google I have concluded that, unlike some other IOC/Mocking product pairings, a ready-made plugin library is not available for LightInject+Nsubstitute to simplify the declaration of do-nothing default mocks in the arrange stage of a unit test.

I have read the LightInject docs on how to override a LightInject container with a temporary enhanced mock object just for the scope of a unit test but what about all the do-nothing default isolation mocks that a unit test might touch. Is there a way to automate their creation within the LightInject container?

The internal IOC container behaviour I am looking for is:

public class LightInject.ServiceContainer
{
..

  public T GetInstance<T)
  {
     if (( this.RegisteredInterfaces.Any( i => i.Itype == T ) == false )
     && ( this.TemporaryUnitTestOverrides.Any( i => i.Itype == T ) == false ))
     && ( /* this container is configured with an automocking delegate */ ))
          return autoMockCreatorDelegate<T>.Invoke();
  }

It seems like LightInject's IProxy and Interceptors provide some internal mock object building blocks but the Nsubstitute library is full featured in comparison.

Clarification on what I mean by default do nothing mock and an enhanced mock.

   // default do nothing mock
   var calculator = Substitute.For<ICalculator>();

   // Enhanced mock that will return 3 for .Add(1,2)
   var calculator = Substitute.For<ICalculator>();
   calculator.Add(1, 2).Returns(3);

Obviously the second enhanced type of mock will need to be crafted locally per unit test.

Champion answered 3/5, 2014 at 13:31 Comment(0)
N
8

I am the author of LightInject and would really like to help you out.

Let me look into this and get back to you. In the meanwhile you might want to check out this library at LightInject.AutopMoq which is a third party contribution to the LightInject container. It uses Moq instead of NSubstitute, but the general concept should be similar to what you are asking for.

That being said, I did some work a while ago that simplifies automocking even further and will take a look at it it and see how that can be integrated with NSubstitute.

Edit

This is a super simple automocking implementation that works with any "substitute" framework.

using System.Diagnostics;
using LightInject;    
using NSubstitute;

public interface IFoo {  }

class Program
{
    static void Main(string[] args)
    {
        var serviceContainer = new ServiceContainer();
        serviceContainer.RegisterFallback((type, s) => true, request => CreateMock(request.ServiceType));            
        var foo = serviceContainer.GetInstance<IFoo>();
        Debug.Assert(foo is IFoo);
    }

    private static object CreateMock(Type serviceType)
    {
        return Substitute.For(new Type[] { serviceType }, null);                                            
    }
}

Best regards

Bernhard Richter

Neogothic answered 4/5, 2014 at 8:56 Comment(1)
@bernhard Thank you for a quick and authoritative response, your example code is exactly the type of solution I was hoping for. I will create a few prototype unit tests and report back here.Champion
C
1

Some feedback as promised in my comment to the accepted answer. I applied the suggestion from the author of LightInject with success in some simple unit tests.

After getting the basics working I decided to hide the Ioc service mocking setup code in a base class plus something I have called a MockingContext, the end result is cleaner lighter unit test code. The mocking context class also ensures that foreach Nsubstitute configured mock type passed to the Ioc service as a short term automock override, there is a matching LightInjet.Service.EndMocking( T ) call. This removes the danger that configured mocks might pollute the auto mocking assumptions of a following unit test.

In the example ClassC depends on IFooA and IFooB (no constructor injection). For the unit test below, IFooA is auto mocked by LightInject without explicit code whereas IFooB is configured via an Nsubstitute call and also passed to LightInject in the MockingContext.Add<>() method.

[TestClass]
public class UnitTest1 : AutoMocking
{
    [TestMethod]
    public void Test_1()
    {
        using (var mc = MockingContext)
        {
             // No need to mention IFooA here, LightInject will auto mock
             // any interface not previously declared to it.

            // Given
            var mockB = mc.Add<IFooB>();
            mockB.MethodY().Returns("Mock Value OOO");
            var sut = new ClassC();

            // When
            var testResult = sut.MethodZ();

            // Then
            Assert.AreEqual(testResult, "MethodZ() received=Mock Value OOO");
        }
    }
Champion answered 8/5, 2014 at 9:54 Comment(2)
Excellent! It would be really nice if you could share the MockingContext class so that future readers can take advantage of it :)Neogothic
@Neogothic - Yes will do once a problem has been resolved which I should raised as an issue on your main Light Inject site. Further testing has revealed that LightInjet.Service.EndMocking() does not work and manually mocked objects were bleeding through into a following unit test. Perhaps EndMocking() gets confused when automocking is in effect (RegisterFallback) and there is no prior concrete registered type for the service to unwind back to? This is not a major setback because I have patched in a LightInject service recreation in my MockingContext dispose method.Champion

© 2022 - 2024 — McMap. All rights reserved.