Faking hardware using FakeItEasy

By | March 22, 2016

This post on Faking hardware using FakeItEasy is based on a simple premise. You should not suffer just because the function you are unit testing invokes some hardware. Writing hardware/device simulators used to be the way out. But with FakeItEasy, it is easy to fake your way out of this. So Let us see how we can fake hardware like a Ninja.

As usual we will first delve into WHY followed by HOW.

WHY
1. You unit test should be testing only the method under test. If you method is making calls to another method, then the lines of code in that method are also getting exercised. Purity of your unit tests is compromised. Any change in the other method can/will have an impact on your unit tests. Brittle unit tests anyone?
2. Worse is if the method under test is making calls to hardware via drivers. Suddenly your unit tests lose consistency. Hardware can be fickle.
3. You want your unit tests to run anywhere. Not on only those machines where the actual hardware is connected. Buildserver anyone?

HOW
First thing first. When dealing with hardware drivers, always create an interface first and then implement the interface for the hardware. Programming to interfaces is always a good idea. And in case of faking hardware using FakeItEasy, it is critical. You will see why.

So let us start with an interface IDriver

namespace FakeItEasyDemo
{
    public interface IDriver
    {
        string GetTransferSpeed(string protocolType, string port);
    }
}

Now that we got the interface part sorted out let us have the actual driver code.

namespace FakeItEasyDemo
{
    public class DemoDriver : IDriver
    {
        public string GetTransferSpeed(string protocolType, string port)
        {
            //Some calculations
            return "123";
        }
    }
}

Now this method GetTransferSpeed will be called in the method which we are unit testing. What I will do is that I will create a fake instance of this driver and pass that instance to my method (Constructor injection is a good thing. Trust me).

We will be testing the method IsModemReady in this class.

using System.IO;

namespace FakeItEasyDemo
{
    public class MethodUnderTest
    {
        private readonly IDriver _driver;

        public MethodUnderTest(IDriver driver)
        {
            _driver = driver;
        }

        public bool IsModemReady()
        {
            int transferRate = _driver.GetTransferSpeed("USB3.0", "COM4");
            if (transferRate == 0)
            {
                throw new IOException("Device is off");
            }
            //And what not
            return true;
        }
    }
}

So your unit test will be something on the lines of:

using FakeItEasy;
using System.IO;
using Xunit;

namespace FakeItEasyDemo
{
    public class Tests
    {
        [Fact]
        public void When_device_is_off_Expect_IOException()
        {
            var fakeDriver = A.Fake<IDriver>();

            A.CallTo(() => fakeDriver.GetTransferSpeed(A<string>._, A<string>._))
                .Returns(0);

            var methodUnderTestInstance = new MethodUnderTest(fakeDriver);
            Assert.Throws<IOException>(() => methodUnderTestInstance.IsModemReady("USB3.0", "COM4"));
        }
    }
}

This test passes.
Hardware faking for unit tests using FakeItEasy
Let us dissect what we did here.
Line 12: We created a fake instance of the driver.
Line 14: We told fakeiteasy that there is a method called GetTransferSpeed and it takes two parameters of type string. When that method is called on the fake object, return 0.
Line 17: Power of constructor injection. Makes testing easy! The method gets an instance of the fake driver.
Line 18: We check that when our method makes the call to the driver, the exception is thrown.

This was the difficult part. That is how to write drivers and client code to make faking hardware using FakeItEasy actually easy and maintainable. Now I will concentrate on using FakeItEasy to make the driver method dance to our tunes.

Case 1:
No matter with what values the driver is called, just return 5.

A.CallTo(() => fakeDriver.GetTransferSpeed(A<string>._, A<string>._))
    .Returns(5);

FakeItEasy here is just told that the GetTransferSpeed is called with two string parameters. But no matter what the values the caller passes, just return 5.

Case 2:
When the parameters are say “USB3.0” and “COM2” then return 6.

A.CallTo(fakeDriver)
    .Where(x => x.Method.Name == "GetTransferSpeed")
    .WithReturnType<int>()
    .WhenArgumentsMatch(x => x.Get<string>(0) == "USB3.0" && x.Get<string>(1) == "COM2")
    .Returns(6);

You have to tell FakeItEasy in this case what is the return type as I did in the 3rd line.
4th line is special. It is telling fake it easy that when the first parameter (see the position, 0. Indexing starts at 0) is “USB3.0” and second parameter is “COM2” return 6.

Case 3:
When the parameters are say “USB3.0” and “COM3” then return 6.
When the parameters are say “USB3.1” and “COM2” then return 9.

A.CallTo(fakeDriver)
    .Where(x => x.Method.Name == "GetTransferSpeed")
    .WithReturnType<int>()
    .WhenArgumentsMatch(x => x.Get<string>(0) == "USB3.0" && x.Get<string>(1) == "COM3")
    .Returns(6);

A.CallTo(fakeDriver)
    .Where(x => x.Method.Name == "GetTransferSpeed")
    .WithReturnType<int>()
    .WhenArgumentsMatch(x => x.Get<string>(0) == "USB3.1" && x.Get<string>(1) == "COM2")
    .Returns(9);

Faking hardware using FakeItEasy isn’t so hard after all, isn’t it?

Case 4:
When the second parameter is say “COM2” then return 7. First parameter does not matter.

A.CallTo(fakeDriver)
    .Where(x => x.Method.Name == "GetTransferSpeed")
    .WithReturnType<int>()
    .WhenArgumentsMatch(x => x.Get<string>(1) == "COM2")
    .Returns(7);

Self explanatory.

Case 5:
When called for the first time, return 4. After that on the next invocation return 5. Then 6. Then 8. Then 10.

A.CallTo(fakeDriver)
    .Where(x => x.Method.Name == "GetTransferSpeed")
    .WithReturnType<int>()
    .WhenArgumentsMatch(x => x.Get<string>(0) == "USB3.0" && x.Get<string>(1) == "COM3")
    .ReturnsNextFromSequence(4, 5, 6, 8, 10);

This will return 4, 5, 6, 8 and 10 on its first, second, third, fourth and fifth invocation when the parameters match.

I hope you realize that Faking hardware using FakeItEasy is really easy and cheap. I will keep adding more examples from my work illustrating the faking of weird hardwares. If you have anything to add to this, let me know in comments.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.