Scroll Top

Unit Testing For Modern Developers Part II: Using Fakes

Share this

Unit Testing For Modern Developers Part II: Using Fakes

Let’s say you have a class that, as part of some useful operation, needs to read a file from disk. Nothing fancy, it’s small enough that you can call File.ReadAllLines and use the resulting array of strings directly.
using System.IO;
using System.Linq;
 
namespace BusinessLogic
{
    public class JankyClass
    {
        public int DoSomeWork()
        {
            string[] values = File.ReadAllLines("hardcoded-external-dependency.text");
 
            var sum = values.Sum(x => int.Parse(x));
 
            return sum;
        }
    }
} 

Looking at that code you’ll notice that line 10 requires an external file to be present and if it’s not found then File.ReadAllLines  will throw FileNotFoundException . This is problematic as we can’t reach line 12 to test it unless we can stage a file containing test data – but that violates the no external dependencies principle mentioned in Part I  We can’t mock out the call to File.ReadAllLines because it’s not injected, the File class does not have an interface and to add insult to injury the function in question is a class static. So what to do?

What if there was a way to intercept that static function from your test method and return known test data? Well, there is and it’s called Microsoft Fakes.

What are Fakes?

A fake is a type of Test Double that is a simplified version of an existing piece of functionality; it has a working implementation that is different from production implementation. Fakes are often used to remove a dependency on a larger, more complex system. For example, you may have a data storage system using Amazon RDS but a filesystem based implementation for use locally.  It’s important to note that a fake must satisfy the Liskov substitution principle for it to be of value. It must conform to the same interface as the system is it replacing.

Another type of test double that is very useful is a stub. A stub is an object that responds to method calls with known data, or known behavior, during tests in order to isolate the system under test from external dependencies. In the past, a team might have a library of stubs 

So what are Microsoft Fakes? Let’s let Microsoft explain it; here’s what MSDN has to say:

Microsoft Fakes helps you isolate the code you are testing by replacing other parts of the application with stubs or shims. These are small pieces of code that are under the control of your tests. By isolating your code for testing, you know that if the test fails, the cause is there and not somewhere else. Stubs and shims also let you test your code even if other parts of your application are not working yet.

 

Fakes come in two flavors:


A stub replaces a class with a small substitute that implements the same interface. To use stubs, you have to design your application so that each component depends only on interfaces, and not on other components. (By "component" we mean a class or group of classes that are designed and updated together and typically contained in an assembly.)


A shim modifies the compiled code of your application at run time so that instead of making a specified method call, it runs the shim code that your test provides. Shims can be used to replace calls to assemblies that you cannot modify, such as .NET assemblies.

Wait, WHAT?

That’s a hot mess of confusion right there. That doesn’t sound at all like the description above, does it?

Let’s clear things up a bit.

What Microsoft is providing here is a technology to aid testing that leverages reflection to replace code in the system under test with implementations that are suitable for testing, e.g, methods that returned known values, always throw exceptions, et cetera. When reading the Microsoft documentation, substitute mock for stub, and stub for shim. 

Let’s set aside half of that confusion right now and focus on stubs (aka MS Shims). From this point forward, I will be using the Microsoft-focused term shim. 

Mocks will be discussed in Part III.

Creating a Simple Test

using BusinessLogic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestUsingFakes
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            // arrange
            var sut = new JankyClass();

            // act
            var sum = sut.DoSomeWork();

            // assert
            Assert.AreEqual(100, sum);
        }
    }
}
 

Above we have a working test for the code presented at the start of this article. TIP: If you ever find your self stuck when trying to write.a test, remember the AAA method: Arrange, Act, Assert.

We first arrange everything we need for this test. Here, we create an instance of the system under test (sut).

Next, we act  by perform the action we want to test.

Finally, we assert that our expected value  matches the actual value Here we assert that the value returned by DoSomeWork is 100.

The code above is a correct, functioning test but it’s a poor test — it fails nearly every time it runs.  It fails because the system under test has a hidden dependency on an external file that doesn’t exist when the test is run. we’ll also assume  that refactoring our JankyClass is off the table. That one developer who understands its inner workings is on vacation or no longer with the company so it’s too risky to change it. So we need to write a test that can “stub out” the call to File.ReadAllLines() to return known test data. We can do this with a shim.

Using Shims

Note: Shims are meant to be used to replace methods that are external to your project. Do not use them to replace code in your project under test.

Before you can create a shim, you must first create a fakes assembly. Do this by locating the assembly containing the method you want to replace in the References section of your test project in the solution explorer. Right-click on that assembly and select Add Fakes Assembly.  Note that this is not currently possible with Visual Studio for Mac (v8.7.7 was the current version at the time of this writing). For our test here, we want to generate a fakes assembly for System.IO.

using BusinessLogic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestUsingFakes
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            // All of our shims should be created within a ShimsContext
            // so that they are scoped to the test. 
            using (ShimsContext.Create())
            {
                // arrange
                // we need to arrange a "detour" so that
                // File.ReadAllLines() returns an array of integers
                System.IO.Fakes.ShimFile.ReadAllLines = () =>
                {
                    return new[] { 10, 20, 30, 40 };
                };

                var sut = new JankyClass();

                // act
                var sum = sut.DoSomeWork();

                // assert
                Assert.AreEqual(100, sum);
            }
        }
    }
}
 
Above is the same test as before but now it’s been modified to replace File.ReadAllLines with a shim that returns data specific to the test. Note that the test is now wrapped in a using statement and scoped to execute within a ShimsContext. Whenever you create a shim, its lifetime must be bounded by a ShimsContext and the best way to ensure they are removed is with a using statement. Shim class names are made up by prefixing Fakes.Shim to the original type name. Here, System.IO.File.ReadAllLines becomes System.IO.Fakes.ShimFile.ReadAllLines(). This just touches the surface of what you can do with a good Fakes library. There are others out there, such as Pose. We’re not going to explore Fakes further here just yet because as of this writing support fore dotnet core is in preview and it isn’t available at all if you’re developing on a Mac. Read more about Shims on MSDN

Further reading

Share this

Related Posts

Privacy Preferences
When you visit our website, it may store information through your browser from specific services, usually in form of cookies. Here you can change your privacy preferences. Please note that blocking some types of cookies may impact your experience on our website and the services we offer.