2

I have a piece of production code where some long lasting task must finish before something else can be done (See example below). I am able to test the call order but not the await (which is missing in the example). What must I do to test this?

using System.Threading.Tasks;
using Moq;
using NUnit.Framework;

namespace TestingCallsAwaitedDemo
{
    public interface IFoo
    {
        Task DoThisFirst();
    }

    public interface IBar
    {
        void ThenDoThis();
    }

    public class Demo
    {
        private readonly IFoo _foo;
        private readonly IBar _bar;

        public Demo(IFoo foo, IBar bar)
        {
            _bar = bar;
            _foo = foo;
        }

        public async Task DoSomething()
        {
            _foo.DoThisFirst(); // The await is missing. How do I force its presence with a test?
            _bar.ThenDoThis();
        }
    }

    [TestFixture]
    public class DemoTests
    {
        private Demo classUnderTest;
        private Mock<IFoo> fooMock;
        private Mock<IBar> barMock;

        [SetUp]
        public void Setup()
        {
            fooMock = new Mock<IFoo>();
            barMock = new Mock<IBar>();
            classUnderTest = new Demo(fooMock.Object, barMock.Object);
        }

        [Test]
        public async Task MustFinishDoThisFirstBeforeCallingThenDoThis()
        {
            var callOrder = string.Empty;
            fooMock.Setup(v => v.DoThisFirst())
                .Callback(() => callOrder += "DoThisFirst >> ");
            barMock.Setup(v => v.ThenDoThis())
                .Callback(() => callOrder += "ThenDoThis >> ");

            await classUnderTest.DoSomething();

            Assert.AreEqual(
                "DoThisFirst >> ThenDoThis >> ",
                callOrder);

            //How do I test the missing await?
        }
    }
}

Here is a solution. Thanks to @dvorn

[Test]
public async Task MustFinishDoThisFirstBeforeCallingThenDoThis()
{
    var tcs = new TaskCompletionSource<bool>();
    fooMock.Setup(v => v.DoThisFirst()).Returns(tcs.Task);

    var task = classUnderTest.DoSomething();

    Assert.IsFalse(task.IsCompleted, "Did not await 'DoThisFirst'");
    fooMock.Verify(f => f.DoThisFirst(), Times.Once);
    barMock.Verify(b => b.ThenDoThis(), Times.Never);

    tcs.SetResult(true);
    await task;

    Assert.IsTrue(task.IsCompleted);
    fooMock.Verify(f => f.DoThisFirst(), Times.Once);
    barMock.Verify(b => b.ThenDoThis(), Times.Once);
}
ChrisM
  • 1,148
  • 8
  • 22
  • 1
    You should await task between SetResult and asserting IsCompleted. Otherwise it is not guaranteed to succeed. – dvorn Sep 08 '16 at 16:17
  • I had that in there at first but it did not seem to make a difference. Anyways, I think you are right because it may fail only sometimes. I'll correct my example. – ChrisM Sep 08 '16 at 16:19
  • 1
    Usually SetResult runs continuations synchronously and it will work: https://stackoverflow.com/questions/12693046/configuring-the-continuation-behaviour-of-a-taskcompletionsources-task. However, it would be wise to be explicit about your intentions. You never know how this code will be modified years from now by another developer... – dvorn Sep 08 '16 at 16:30

1 Answers1

1

TaskCompletionSource<bool> tcs;

Setup that fooMock returns tcs.Task

var task = classUnderTest.DoSomething();

Assert that task is not completed.

tcs.SetResult(true).

Now await task.

dvorn
  • 3,107
  • 1
  • 13
  • 12