Unit Testing Part 2

In the last post, I started covering unit tests. Specifically, I talked about how only testing one unit at a time can help us with debugging. I also covered the basic usage of xUnit.

Understanding basic unit testing concepts can get you a long way, but before too long, you’ll find that you need some more advanced techniques. This post will cover mocking interfaces in order to test code that interacts with outside applications (such as databases).

When it comes to writing testable code, interfaces are your friends. Interfaces make your code cleaner and easier to test. Here is the classic barn yard example (adapted from https://spin.atomicobject.com/2014/04/29/code-untestable-part-2-developers/).

public class Pig {
    public void Oink() {
        Console.WriteLine("Oink!");
    }
}

public class Cow {
    public void Moo() {
        Console.WriteLine("Moo!");
    }
}

public class Rooster {
    public void Crow() {
        Console.WriteLine("Crow!");
    }
}

public class BarnYard {
    public void Noises() {
        var pig = new Pig();
        var cow = new Cow();
        var rooster = new Rooster();

        pig.Oink();
        cow.Moo();
        rooster.Crow();
    }
}

There are a couple problems with this. First of all, it can be made simpler.¬†Wouldn’t it be easier if instead of having to call a different method for each animal, we could instead just call one for all the animals? Also, if we needed to mock this code, we would have one mock per animal. Instead, let’s implement an interface.

public interface Animal {
    public void Vocalize();
}

public class Pig : Animal {
    public void Vocalize() {
        Console.WriteLine("Oink!");
    }
}

public class Cow : Animal {
    public void Vocalize() {
        Console.WriteLine("Moo!");
    }
}

public class Rooster : Animal {
    public void Vocalize() {
        Console.WriteLine("Crow!");
    }
}

public class BarnYard {
    public void Noises() {
        var animals = new List<Animal>()
        animals.Add(new Pig());
        animals.Add(new Cow());
        animals.Add(new Rooster());

        foreach (var animal in animals) {
            animal.Vocalize();
        }
    }
}

Now the BaryYard will work with any type of animal and we only have one mock to build if we want to test it.

Let’s move to a more concrete example and use our FlightSim application and a .NET mocking framework named ‘Moq‘.

In the FlightSim.Service.FlightController class there is this method:

public bool IsDuplicateFlight(Flight flight)
{
    var flights = Get(flight.Number);

    if (flights.Any())
    {
        return false;
    }
    return true;
}

When the user creates or updates a flight, the system checks to make sure there are no other flights with the new flight’s flight number. To do this, the database is queried and duplicate flights are returned.

When we test this method, we don’t want the database to be queried because the machine running the unit test may not have access to a database. Also, testing with a database would go against our principle of only testing one thing at a time. So, instead, let’s test it using a mock.

using FlightSim.Data;
using FlightSim.Entity;
using Moq;
using System;
using System.Collections.Generic;
using Xunit;

namespace FlightSim.Service.Tests
{
    [Trait("Category", "FlightController")]
    public class WhenICreateADuplicateFlight
    {
        public bool _isDuplicate;

        public WhenICreateADuplicateFlight()
        {
            var mockContext = new Mock<IContext>();
            mockContext.Setup(c => c.Get(It.IsAny<Func<Flight, bool>>()))
                .Returns(new List<Flight>()
                {
                    new Flight("123", "Denver", "Pittsburgh", DateTime.UtcNow, DateTime.UtcNow, new Airplane("Cessna", 15, 5, 3))
                });

            var controller = new FlightController(mockContext.Object);

            var flight = new Flight("123", "Pittsburgh", "Denver", DateTime.UtcNow, DateTime.UtcNow, new Airplane("Cessna", 15, 5, 3));
            _isDuplicate = controller.IsDuplicateFlight(flight);
        }

        [Fact]
        public void IsDuplicateFlightShouldReturnTrue()
        {
            Assert.True(_isDuplicate);
        }
    }
}

You can see here, that we create an instance of the FlightController with a mocked instance of IContext. We use Moq’s ‘Setup’ method to mock the IContext’s ‘Get’ method.¬†Now instead of connecting to and querying a database, a static list is returned to the application.

An especially powerful syntax is It.IsAny<>(). You can use this when your mocks are parameterized. It tells the mock to return the default value for parameter value. You can include a data type between the brackets to limit the parameter type.

That’s all for this post. I hope you enjoy unit testing and building mocks for your unit test.

Leave a Reply

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