Unit Testing Methods With Random Elements (in MVVM Cross)

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<T>(this IEnumerable<T> enumeration)
        {
            var service = Mvx.Resolve<IRandomService>();            
            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<IRandomService, RandomService>();
        }

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;
            }                
        }
    }
&#91;/sourcecode&#93;

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

<strong>MVVM Cross Unit Testing</strong>

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

<a href="http://pmichaels.net/wp-content/uploads/2014/07/mvvmtest.png"><img src="http://pmichaels.net/wp-content/uploads/2014/07/mvvmtest.png?w=300" alt="mvvmtest" width="300" height="32" class="alignnone size-medium wp-image-618" /></a>


This will add Cirrious.MccmCross.Test.Core:

<a href="http://pmichaels.net/wp-content/uploads/2014/07/refs.png"><img src="http://pmichaels.net/wp-content/uploads/2014/07/refs.png" alt="refs" width="239" height="211" class="alignnone size-full wp-image-620" /></a>

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); } } } } [/sourcecode] 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.

Leave a Reply

Your email address will not be published. Required fields are marked *