Unit Test Invocation On A Different Thread

No Comments March 1, 2013

A colleague nabbed me yesterday with a problem he needed some help with; he had a block of code that (in a vastly simplified form) looked like this:

public interface IFoo {
    void ExecuteTask(string data);
}

public interface IBar {
    void DoWork(string data);
}

public class Foo : IFoo {
    private readonly IBar _bar;

    public Foo(IBar bar)
    {
        _bar = bar;
    }

    public void ExecuteTask(string data)
    {
        new Task(() => _bar.DoWork(data)).Start();
    }
}

He also had a unit test that covered ExecuteTask that looked like this:

[TestFixture]
public class FooTests
{
    [Test]
    public void ExecuteTaskShouldInvokeDoWorkWithCorrectParameter()
    {
        var kernel = new MoqMockingKernel();
        var bar = kernel.GetMock<IBar>();

        var foo = kernel.Get<Foo>();
        foo.ExecuteTask("test data");

        bar.Verify(b => b.DoWork("test data"));
    }
}

This worked fine, and NCrunch was reporting that the test worked with bright green dots in the left hand margin. All well and good, except that the moment that ExecuteTask was extended to do anything before invoking DoWork the test would fail. So, if the ExecuteTask implementation looked like this:

public void ExecuteTask(string data)
{
    new Task(() => {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        _bar.DoWork(data);
    }).Start();
}

control would be passed back to the test before DoWork had been invoked and therefore the test would fail. One way to get around this issue is to use a form of Test Spy to set a ManualResetEvent when DoWork is called and record the parameter value for later inspection.

[TestFixture]
public class FooTests
{
    [Test]
    public void ExecuteTaskShouldInvokeDoWorkWithCorrectParameter()
    {
        var kernel = new MoqMockingKernel();
        var bar = new BarSpy();
        kernel.Bind<IBar>().ToConstant(bar);

        var foo = kernel.Get<Foo>();
        foo.ExecuteTask("test data");

        bar.WaitOne(TimeSpan.FromSeconds(5)).Should().BeTrue();
        bar.Data.Should().Be("test data");
    }

    public class BarSpy : IBar
    {
        private readonly ManualResetEvent _manualResetEvent;

        public BarSpy()
        {
            _manualResetEvent = new ManualResetEvent(false);
        }

        public string Data { get; private set; }

        public void DoWork(string data)
        {
            Data = data;
            _manualResetEvent.Set();
        }

        public bool WaitOne(TimeSpan timeSpan)
        {
            return _manualResetEvent.WaitOne(timeSpan);
        }
    }
}

So in this instance we create an instance of BarSpy which has two methods (WaitOne and Data) that are not defined in the IBar interface, and bind it to the mocking kernel. We can then execute the ExecuteTask method on Foo and wait for BarSpy to signal that its DoWork method has actually been invoked.


No Comments