Unit Testing Methods With Random Elements (in MVVM Cross)

July 27, 2014

Okay, quick spoiler for this: you can’t. You can’t, not really; obviously, you can write the test, but unit tests should be predictive, and a random element should not.

Solution

I imagine there are a few ways of solving this. The way shown in this post is specific to MVVM cross, but should work with any system that uses an IoC container. In brief, we’re simply going to mock out the system Random class.

How?

Well, since System.Random is the domain of Microsoft, we’ll start with a wrapper; and since this is MVVM Cross, we’ll make it a service:


    class RandomService : IRandomService
    {
        private static Random \_rnd = null;

        public virtual int SelectRandomNumber(int max)
        {
            if (\_rnd == null)
            {
                \_rnd = new Random();
            }

            return \_rnd.Next(max);
        }
    }

Couple of notes on this: 1. I haven’t posted the interface but it’s just the one method. 2. The reason for the Random class being static is that the random seed is taken from the system clock, meaning that if you call this in quick succession, there is a possibility that you would get the same number returned. 3. This is not thread safe.

Okay - all that out of the way, the code is pretty basic. Now let’s call it:


        public static T SelectRandomElement(this IEnumerable enumeration)
        {
            var service = Mvx.Resolve();            
            int idx = service.SelectRandomNumber(enumeration.Count() + 1);

            return enumeration.ElementAt(idx);
        }

Right, so you’ll recognise the extension method from the last post, but now it retrieves the instance of the random service; here’s where we register that:


        protected override void InitViewModel()
        {
            Mvx.ConstructAndRegisterSingleton();
        }

You can actually register it anywhere you like… before it’s actually called.

Okay, so now we should have unchanged functionality; everything works as before.

The Unit Tests

The first task here is to create the mock RNG:


    class MockRandomService : IRandomService
    {
        static int \_lastNumber = 0;

        public int SelectRandomNumber(int max)
        {            
            if (\_lastNumber < max)
                return ++\_lastNumber;
            else
            {
                \_lastNumber = 0;
                return \_lastNumber;
            }                
        }
    }

This not allows me to determine what the next random number will be.

MVVM Cross Unit Testing

To set-up a test for MVVM Cross using the IoC container, you need to add some additional libraries to the test project first:

mvvmtest

This will add Cirrious.MccmCross.Test.Core:

refs

And that is important, because it allows you to declare your test class as follows:


    [TestClass]
    public class ExtensionMethodTests : MvxIoCSupportingTest
    {

Inheriting from MvxIoCSupportingTest allows you to call base.Setup(), which prevents the IoC container from crashing when you call it in a test. Here’s the full unit test code:


    [TestClass]
    public class ExtensionMethodTests : MvxIoCSupportingTest
    {
        [TestMethod]
        public void TestSelectRandomElement()
        {
            base.Setup();
            
            Mvx.ConstructAndRegisterSingleton();

            List testCollection = new List();

            testCollection.Add(1);
            testCollection.Add(3);
            testCollection.Add(5);
            testCollection.Add(7);
            testCollection.Add(9);

            // Cycle through all elements
            for (int i = 0; i <= 5; i++)
            {
                int e = testCollection.SelectRandomElement();
                Assert.AreNotEqual(e, 0);
            }
        }

        [TestMethod]
        public void TestSelectRandomElementPredicate()
        {
            base.Setup();

            Mvx.ConstructAndRegisterSingleton();

            List testCollection = new List();

            testCollection.Add(1);
            testCollection.Add(3);
            testCollection.Add(5);
            testCollection.Add(7);
            testCollection.Add(9);

            // Cycle through all elements
            for (int i = 0; i <= 5; i++)
            {
                int e = testCollection.SelectRandomElement(n => n < 2);
                Assert.AreNotEqual(e, 1);
            }
            
        }

    }
}

Conclusion

So, I now have a custom RNG and unit tests that will tell me what happens when I call the method for each element. Obviously these tests are not exhaustive, but they are deterministic.



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024