Mock Objects for Unit Testing: A Beginner's Guide

Mock Objects for Unit Testing: A Beginner's Guide

Learn how mocking can make your unit tests more efficient

Related: Practical Testing Techniques for Windows Azure Applications

If you're interested in unit testing, you've probably heard about mocking. The two topics go hand in hand. For developers, unit testing is a proven, effective, and cheap way to maintain code over the long haul. Thus it makes sense to learn about mocking as well.

Mocking as a skill and mocking frameworks as a tool are handy things to have in your developer tool belt. In this article, I'll discuss what mocking is, how it helps in writing unit tests, and the different ways to do mocking. I will also discuss how you can use this information to improve your tests and avoid maintenance issues.

Unit Tests

Wikipedia defines unit testing as "a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use." Above all, unit tests are a tool to produce quality code, so they should be effective for that purpose.

Effective unit tests have these features:

  • Quick: Unit tests run very quickly. This means you can run hundreds and thousands of unit tests in a matter of minutes or even seconds.
  • Focused: When a unit test fails, it points you directly to where the problem is.
  • Deterministic: Unless the code changed, every time you run the same unit test, you'll get the same result.
  • Independent: Running tests in any order, whether alone or in a group, will always produce the same result.

When you have a suite of effective unit tests, you've struck gold. However, most people struggle to attain this goal. They either don't write unit tests at all, or their tests are not very effective.

Related: Fundamentals of Unit Testing in ASP.NET

Legacy Code

If unit testing is as important as most developers believe, why do so few developers actually do it? The problem with implementing unit testing is that you don't start from scratch as you would when developing a brand-new application. Typically unit testing is performed on legacy code—"code that works" (at least, you think it does).

There are many definitions of legacy code, but I like Michael Feathers' definition: "code without tests." If you are starting out in unit testing, "code without tests" is what you have to work with. Writing tests for legacy code is difficult because legacy code contains many dependencies. Developers typically build dependencies into their applications—for example, frameworks, third-party tools, calls to the database, and reading of configuration files. So the tests you write for such code will also call these dependencies and test them together with the rest of the application code.

Now, including dependencies in your unit tests might not be such a problem if doing so did not conflict with our standards for effective tests:

  • Quick: If our code calls the database and takes three seconds to run, how long will a suite of hundreds of tests run? We're way off our targets of minutes and seconds.
  • Focused: If we're testing our business logic, the data-access layer, the database, and the entire configuration needed to run it, and the test fails—where's the problem located? We'll need to debug in order to find what broke the test, and that's a monstrous time waster.
  • Deterministic: If our code checks the computer clock, for example, tests will give different results based on when they run. That's hardly deterministic.
  • Independent: Let's say one test creates a connection to the database and another test uses it. When they run one after another, everything's fine. But when you run the second test only, the test will crash.

It seems our legacy code is not ready to be tested. In other words, it is not testable. In general, code is testable if we can replace the dependencies easily. We would like "easily" to mean "as-is"—the code is testable without any need to change it. However, we're usually not that lucky.

Testability is a production code characteristic that depends on the technology and language the code is written in. For example, in dynamic languages such as Ruby, code is testable by default. That means you don't need to change the production code to replace the dependencies. Ruby allows you to change method behavior at runtime, which allows a test to redefine a method (for example, to return a specific time reading, rather than go to the computer clock) that behaves differently under testing than it does in production.

Type-safe languages, such as C# or Java, are trickier. There's no easy way to redefine methods in a type-safe language. Since you can't really replace that call to the computer clock, you'll need to change the code around it to make call substitution work. Let's look at the example in Figure 1, which shows the C# class and method to be tested.

public class ExpirationChecker
{
    public bool HasExpired()
    {
        DateTime expirationDate = new DateTime(2014,1,1);
        if (DateTime.Now < expirationDate)
            return false;
        return true;
    }
}

The dependency in the HasExpired method is the call to DateTime.Now. Under normal circumstances, it accesses the computer clock at runtime. For the purpose of testing, we'd like to replace the call to DateTime.Now with a value that is not dependent on the system clock. However, DateTime.Now is a static property that cannot be set externally. For this to work, we'll need to abstract the call to DateTime.Now through an interface.

Let's define a new interface:

public interface IClockAccess
{
    DateTime GetTimeNow { get; }
}

This interface will help us get access to the computer clock. We'll need to define a ClockAccess class that implements the interface, so that we can use it in the production code:

public class ClockAccess : IClockAccess
{
    public DateTime GetTimeNow
    {
        get { return DateTime.Now; }
    }
}

Finally, we'll change our HasExpired method to use this interface:

public bool HasExpired(IClockAccess clockAccessor)
{
    DateTime expirationDate = new DateTime(2014,1,1);
    if (clockAccessor.GetTimeNow < expirationDate)
        return false;
    return true;
}

So far, all we've done is refactoring: We've changed the production code but not its functionality. But now we can call HasExpired on a FakeClockAccess class, as shown in Figure 2.

public class FakeClockAccess : IClockAccess
{
    DateTime fakeDateTime;

    public void SetDate(DateTime newDate)
    {
        fakeDateTime = newDate;
    }

    // From IClockAccess
    public DateTime GetTimeNow
    {
        get { return fakeDateTime; }
    }
}

The fake class also implements IClockAccess, but now we'll add a setter method to control what the code returns. Note that this FakeClockAccess is part of the test code, not the production code. Finally, we can write the tests shown in Figure 3 to cover both cases: before and after expiration.

[TestMethod]
public void HasExpired_BeforeExpiration_False()
{
    FakeClockAccess fakeClockAccess = new FakeClockAccess();
    fakeClockAccess.SetDate(new DateTime(2000, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsFalse(checker.HasExpired(fakeClockAccess));
}

[TestMethod]
public void HasExpired_AfterExpiration_True()
{
    FakeClockAccess fakeClockAccess = new FakeClockAccess();
    fakeClockAccess.SetDate(new DateTime(2020, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsTrue(checker.HasExpired(fakeClockAccess));
}

To recap, we've introduced an interface to the method that allows us to access the computer clock in production. During testing, this method lets us pass a fake, controllable date.

The fakeClockAccess class is what we call a hand-rolled mock. In other words, we're creating a class that is used in testing rather than in production. The hand-rolled part is about us coding that class.

There are a few problems with this approach. The first is that regardless of how we're creating the fake object, doing so requires changing the production code. Depending on the language and tools used, creating a hand-rolled mock might be easy or difficult. More importantly, doing so could be risky. After all, we're modifying our code so that we can write tests for it, because we don't have tests for it. Simple modifications might be possible; bigger ones can introduce bugs into the code.

Another problem is the actual coding of the fake objects. In the beginning, the fake objects are very easy to create. But once you start testing all kinds of cases, you start duplicating code between the production and test code. That is a recipe for maintenance disaster. At some point, there will be some modification in the production code that you will forget to pass to the test code. This will cause tests to break for the wrong reason. In turn, you'll spend maintenance time on fixing the tests, and you'll probably start to dislike unit testing.

Mocking Frameworks

Mocking frameworks are the solution to the second problem, and some of them also help with the first one. Mocking frameworks are tools that help define the fake objects, but without the coding. For example, once we've introduced the IClockAccess interface, we can use a mocking framework to create a fake object and set the behavior without any coding. Figure 4 provides an example of how to perform both of these actions using Typemock Isolator, a .NET mocking framework.

[TestMethod]
public void HasExpired_BeforeExpiration_False()
{
    IClockAccess fakeClockAccess = Isolate.Fake.Instance<IClockAccess>();
    Isolate.WhenCalled(() => fakeClockAccess.GetTimeNow)
        .WillReturn(new DateTime(2000, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsFalse(checker.HasExpired(fakeClockAccess));
}

[TestMethod]
public void HasExpired_AfterExpiration_True()
{
    IClockAccess fakeClockAccess = Isolate.Fake.Instance<IClockAccess>();
    Isolate.WhenCalled(() => fakeClockAccess.GetTimeNow)
        .WillReturn(new DateTime(2020, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsTrue(checker.HasExpired(fakeClockAccess));
}

This time, we don't build our own fake class but instead use Isolator to create a fake object that implements IClockAccess. Then we set the behavior of GetTimeNow to return what we need.

Most frameworks in type-safe environments let you change the behavior of interfaces (or abstract classes) and override virtual methods. Some frameworks, such as PowerMock in Java and Typemock Isolator in .NET, let you mock any method calls, including static calls. For example, we can test the original code as shown in Figure 5.

[TestMethod]
public void HasExpired_BeforeExpiration_False()
{
    Isolate.WhenCalled(() => DateTime.Now)
        .WillReturn(new DateTime(2000, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsFalse(checker.HasExpired());
}

[TestMethod]
public void HasExpired_AfterExpiration_True()
{
    Isolate.WhenCalled(() => DateTime.Now)
        .WillReturn(new DateTime(2020, 1, 1));

    ExpirationChecker checker = new ExpirationChecker();
    Assert.IsTrue(checker.HasExpired());
}

Apart from letting you change the behavior of method calls, mocking frameworks offer another advantage. They allow testing calls against dependencies.

In our previous example, HasExpired returns a value that we can check. We use the return value to assert (i.e., verify that HasExpired ran correctly). Sometimes we're not so lucky, and we can't assert on anything. Consider this: Let's say our ExpirationChecker has another method, ReportIfExpired, which sends an email through an Administrator class:

public void ReportIfExpired()
{
    DateTime expirationDate = new DateTime(2014, 1, 1);
    if (DateTime.Now > expirationDate)
        Administrator.SendExpiredEmail();
}

How do you test this method? It doesn't return a value. Theoretically, we could send emails and check the administrator's email inbox to see whether those messages arrived, but doing so might annoy our admin.

Clearly the SendExpiredEmail method is a good candidate for faking. We don't want to really send an email to the administrator, but we'd like to make sure that under the right conditions, the method was called. This is where mocking frameworks help us: They can check whether methods were called and perform a check that can pass or fail the test, as shown in Figure 6.

public void ReportIfExpired_AfterExpiration_SentEmail()
{
    Isolate.WhenCalled(() => DateTime.Now)
        .WillReturn(new DateTime(2020, 1, 1));
    Isolate.WhenCalled(() => Administrator.SendExpiredEmail())
        .IgnoreCall();

    ExpirationChecker checker = new ExpirationChecker();
    checker.ReportIfExpired();

    Isolate.Verify.WasCalledWithAnyArguments(() => Administrator.SendExpiredEmail());
}

In the code in Figure 6, we first fake the future DateTime to make sure the test creates an expiration. Then we fake the SendExpiredEmail, to avoid filling the administrator's inbox with messages.

The assert is replaced with Verify.WasCalledWithAnyArguments, specifying the method we expect to be called (but not executed). If we don't call the method in our code, the test will fail. If the method had parameters, we can check them as well.

Note that mocking frameworks allow verification of calls that they can fake. If the framework supports just faking virtual methods and abstract classes, it can verify only those calls.

Mocking Tips

As with any developer tool, learning how to use a mocking framework requires learning a new skill set to use it effectively in your testing. Here are several tips to keep in mind when using mocking in your test environment:

  • Before beginning to fake everything in sight, identify what is a true dependency. Remember that we want quick feedback but also increased coverage. We want to exercise as much code as we can, as long as the tests run quickly. Excellent candidates for mocking are calls to databases and calls to the file system, which can slow tests down.
  • Use mocking in moderation. Too many fake objects produce fragile tests and create a situation where your mocking setup is susceptible to breaking when the interfaces change.
  • Opt to assert values instead of verifying method calls. Testing against an exposed state is more reliable than testing for method calls.

Ready, Set, Fake!

You now have an understanding of the basics of mocking and mocking frameworks. As you write more tests, you'll gain more experience about where to use mocking, what options you have, and how to improve existing tests. Without mocking, unit testing becomes impractical. I encourage you to learn about mocking frameworks in your working language and to start using them in tests around your production code.

Related: Typemock Offers Free .NET Unit Testing and Mocking Tool: Isolator Basic

****************************************

Gil Zilberfeld is a product manager at Typemock working as part of an agile team that creates tools for agile developers. He promotes unit testing and other design practices, including down-to-earth methods and some incredibly cool tools.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish