Unit Testing asynchronous code

Sep 7, 2010 at 8:46 AM

I've become very fond of the whole IResult concept in combination with the yield statement because it results in very readable and maintainable code. However, now that my first sprint has completed, I wanted to start picking up the TDD practice again. I'm a very experienced TDD and BDD practitioner but I still noticed some interesting challenges. 

For one, testing a view model that uses an IResult implementation which on turn uses some kind of web-service facade interface proved to be very difficult. I'm now considering using some kind of factory interface that a view model uses to create my coroutines. I can then inject mocks/stubs in my view model to test the effect of executing several coroutines.

 

What do other Caliburn users do? Is there any guidance?

Coordinator
Sep 7, 2010 at 1:23 PM

Here's a few ideas.

1. Use property injection for IResults. All IResult implementations pass throught IoC.BuildUp to allow your container an opportunity to inject services. Only use the constructor for local parameter data.

2. When testing a coroutine, simply enumerate each result, one at a time. Do not call execute on your results. Remember, you are not testing the IResult implementation, but the coroutine implementation.

3. Check to make sure that the correct IResult is yielded and check any properties it has which would indicate it's correct/incorrect configuration.

4. Set any properties on the IResult which would have been set as a result of execution....but do not call execute.

5. Go to step 2

By doing this you will be able to "ignore" the async part of the coroutine (if it has one) by exerting direct control over the enumeration. You can even choose to stop enumerating at any point once you have confirmed that your test passes/fails. You probably want to use several test methods to test a single coroutine implementation in order to keep individual tests simple and small.

Here's a simple sample class you might use to help in enumerating the results:

    public class TestResultEnumerator
    {
        private readonly IEnumerator<IResult> _enumerator;

        public TestResultEnumerator(IEnumerable<IResult> enumerator)
        {
            _enumerator = enumerator.GetEnumerator();
        }

        public T Next<T>()
            where T : IResult
        {
            if(_enumerator.MoveNext())
                return (T)_enumerator.Current;
            return default(T);
        }
    }

 

 

Sep 7, 2010 at 5:36 PM

Excellent ideas Rob. I was already doing steps 1 - 4, but didn't think about setting the properties of an IResult. One thing I've been struggling with is the choice of whether the IResult implementations should be treated as being a part of the view model 'unit'.

You should tranform this post in a recipe or something alike.

Coordinator
Sep 7, 2010 at 5:40 PM

Good idea. I added a ticket for that.

Sep 9, 2010 at 9:09 AM

I modified you're enumerator a bit so that you can Next<> to a specific coroutine in case you don't care for the others, and have a Finish() that simply completes the enumeration.

    public class CoroutineEnumerator
    {
        private readonly IEnumerator<IResult> enumerator;

        public CoroutineEnumerator(IEnumerable<IResult> enumerator)
        {
            this.enumerator = enumerator.GetEnumerator();
        }

        public TCoroutine Next<TCoroutine>()
        {
            while (enumerator.MoveNext())
            {
                if (enumerator.Current is TCoroutine)
                {
                    return (TCoroutine)enumerator.Current;
                }
            }

            throw new InvalidOperationException("List of coroutines does not include " + typeof (TCoroutine).Name);
        }

        public void Finish()
        {
            while (enumerator.MoveNext()) 
            {}
        }
    }