Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing with Mocks when SUT is leveraging Task Parallel Libaray

I am trying to unit test / verify that a method is being called on a dependency, by the system under test (SUT).

  • The depenedency is IFoo.
  • The dependent class is IBar.
  • IBar is implemented as Bar.
  • Bar will call Start() on IFoo in a new (System.Threading.Tasks.)Task, when Start() is called on Bar instance.

Unit Test (Moq):

    [Test]
    public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist()
    {
        //ARRANGE

        //Create a foo, and setup expectation
        var mockFoo0 = new Mock<IFoo>();
        mockFoo0.Setup(foo => foo.Start());

        var mockFoo1 = new Mock<IFoo>();
        mockFoo1.Setup(foo => foo.Start());


        //Add mockobjects to a collection
        var foos = new List<IFoo>
                       {
                           mockFoo0.Object,
                           mockFoo1.Object
                       };

        IBar sutBar = new Bar(foos);

        //ACT
        sutBar.Start(); //Should call mockFoo.Start()

        //ASSERT
        mockFoo0.VerifyAll();
        mockFoo1.VerifyAll();
    }

Implementation of IBar as Bar:

    class Bar : IBar
    {
        private IEnumerable<IFoo> Foos { get; set; }

        public Bar(IEnumerable<IFoo> foos)
        {
            Foos = foos;
        }

        public void Start()
        {
            foreach(var foo in Foos)
            {
                Task.Factory.StartNew(
                    () =>
                        {
                            foo.Start();
                        });
            }
        }
    }

Moq Exception:

*Moq.MockVerificationException : The following setups were not matched:
IFoo foo => foo.Start() (StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() in
FooBarTests.cs: line 19)*
like image 426
holsee Avatar asked Apr 29 '10 10:04

holsee


1 Answers

@dpurrington & @StevenH: If we start putting this kind of stuff in our code

sut.Start();
Thread.Sleep(TimeSpan.FromSeconds(1)); 

and we have thousands of "unit" tests then our tests start running into the minutes instead of seconds. If you had for example 1000 unit tests, it will be hard to have your tests to run in under 5 seconds if someone has gone and littered the test code base with Thread.Sleep.

I suggest that this is bad practice, unless we are explicitly doing Integration testing.

My suggestion would be to use the System.Concurrency.IScheduler interface from System.CoreEx.dll and inject the TaskPoolScheduler implementation.

This is my suggestion for how this should be implemented

using System.Collections.Generic;
using System.Concurrency;
using Moq;
using NUnit.Framework;

namespace StackOverflowScratchPad
{
    public interface IBar
    {
        void Start(IEnumerable<IFoo> foos);
    }

    public interface IFoo
    {
        void Start();
    }

    public class Bar : IBar
    {
        private readonly IScheduler _scheduler;

        public Bar(IScheduler scheduler)
        {
            _scheduler = scheduler;
        }

        public void Start(IEnumerable<IFoo> foos)
        {
            foreach (var foo in foos)
            {
                var foo1 = foo;  //Save to local copy, as to not access modified closure.
                _scheduler.Schedule(foo1.Start);
            }
        }
    }

    [TestFixture]
    public class MyTestClass
    {
        [Test]
        public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist()
        {
            //ARRANGE
            TestScheduler scheduler = new TestScheduler();
            IBar sutBar = new Bar(scheduler);

            //Create a foo, and setup expectation
            var mockFoo0 = new Mock<IFoo>();
            mockFoo0.Setup(foo => foo.Start());

            var mockFoo1 = new Mock<IFoo>();
            mockFoo1.Setup(foo => foo.Start());

            //Add mockobjects to a collection
            var foos = new List<IFoo>
                       {
                           mockFoo0.Object,
                           mockFoo1.Object
                       };

            //ACT
            sutBar.Start(foos); //Should call mockFoo.Start()
            scheduler.Run();

            //ASSERT
            mockFoo0.VerifyAll();
            mockFoo1.VerifyAll();
        }
    }
}

This now allows the test to run at full speed without any Thread.Sleep.

Note that the contracts have been modified to accept an IScheduler in the Bar constructor (for Dependency Injection) and the IEnumerable is now passed to the IBar.Start method. I hope this makes sense why I made these changes.

Speed of testing is the first and most obvious benefit of doing this. The second and possibly more important benefit of doing this is when you introduce more complex concurrency to your code, which makes testing notoriously difficult. The IScheduler interface and the TestScheduler can allow you to run deterministic "unit tests" even in the face of more complex concurrency.

like image 140
Lee Campbell Avatar answered Oct 08 '22 02:10

Lee Campbell