Fundamentals of Unit Testing in ASP.NET

Fakes, Mocks, Testability, and Coding Practices

Especially in large applications, unit tests are a formidable tool for a development team to leverage—as well as a delicate weapon to manipulate effectively. Unit tests help give developers confidence about the quality of the software being written. And they help answer critical questions such as: "Are we doing well?" "Are we on the right track?" "Does it work?" "Does it break other components?"

To be effective, unit tests should be quick to write and even quicker to run. This leads to a need for a large number of extremely simple tests; ideally, one for each significant feature of each class. Not all the code you might write is inherently testable. You must take special effort to make a piece of code easy to test. Even though the focus on testability is a relatively recent trend in the ASP.NET space, testability is nothing new in software development. Testability is expressly listed as a required attribute of any software architecture by the international standard ISO/IEC 9126. Since 1991, this standard defines a general set of quality characteristics required in software products. Based on this standard, testability is critical to guaranteeing the quality of a software system. Therefore, you should plan for testing very early in the design phase. Yes, but how?

Simplicity and Control

The main trait of testable code is simplicity. However, simple code should not be mistaken for trivial code. In this context, simplicity is not an attribute of functionality; it's an attribute of the way in which you design and write the code. Complex code can—and whenever possible, should—be written in a simple way: with a minimum number of instructions and a clear and easy-to-understand workflow, and articulated in watershed compartments.

Structuring code in watershed compartments makes it easy to determine what goes in and out of each compartment and causes you to define loose and interface-based connections between modules. Designing software in this way adds control to the final result. Control is essential in testing because it enables passing fixed values to each module—both correct and incorrect—to test the behavior.

Testing components individually is another aspect of testing. Especially in a large application, it's not unusual for classes to have dependencies on one another. This is not a bad thing per se, because in software we don’t want to eliminate dependencies. All we want is a set of tools to control dependencies and coupling.

In an overall architecture where components are separated and isolated behind public interfaces, you get a double level of control. You can control not only which values are passed to each component, but also which component is passed as a dependency to an innermost module. In testing, this aspect is often referred to as a test double.

Test Doubles

The expression “test double” refers to components that look like clones of other objects. Test doubles fits nicely in a scenario where complex functionality is obtained by composing various modules together, much like the pieces of a puzzle. Imagine you have a controller class that needs to perform some data access operations via Entity Framework or perhaps through plain ADO.NET calls. By wrapping data access code in an ad hoc component—commonly referred to as a data repository—you streamline the process of unit testing the controller. In fact, when testing the controller you're primarily interested in checking the behavior of the controller class, not any of its dependencies. In particular, you can inject a fake data access component and assume that any interaction between the controller and data access code occurs successfully. Your controller tests, therefore, will be focused on the way in which the controller processes input values and passes data down to the view.

You have two main types of test doubles: fakes and mocks. The difference between the two is in the amount of logic that each contains. In a nutshell, you use a fake object when you intend to ignore a given dependency and pretend the class being tested works as if there were no other component in the middle. For example, a fake object is perfect for isolating a class from some of its cross-cutting aspects such as logging, localization, and security. A mock is ideal when your intent is to interact with the dependency in a controlled way. A mock is a special version of the dependency object that behaves exactly the way you want, simulating successful and unsuccessful calls and returning right and wrong values as appropriate.

Dealing with Fake Objects

A fake object is a plain class that implements the interface that characterizes the dependency to isolate. A fake class will be a derived class if the dependency has origin in a base class. To achieve loose coupling, you can choose an interface or a base class. Class versus interface is a dilemma of software development that you typically resolve using a simple rule of thumb: an interface is always preferable because it saves you the base class for other purposes—an interesting asset in single-inheritance languages like the .NET languages. However, if implementing the interface is expensive (i.e., too many members requiring nontrivial logic) or repetitive (always the same boilerplate code over and over again), choosing a base class is a better option because it saves you valuable time. Base classes are the most frequently used option.

As far as fakes are concerned, you have the problem of being able to inherit from the class you want to mock up for testing purposes. Consider the simple but largely illustrative code in Figure 1.

The class SimpleCalculator depends on an object that implements the ICalcView interface used to notify the user interface about the operation being performed. When first testing the SimpleCalculator class, you might want to ensure that it performs additions and subtractions correctly, not that it displays the current operation correctly. If you want to test the operation, that would be the subject of another round of tests.

Nonetheless, the SimpleCalculator class depends on ICalcView. Because the interaction is limited to invoking a method in a sort of fire-and-forget protocol, you can just skip over the call to that method and be happy.

A fake is a relatively simple clone of an object that offers the same interface and returns hard-coded (or programmatically set) values, if required. A fake holds no state and no significant behavior, but it still provides a fully working implementation. It's usually a derived class. If getting a derived class proves hard because the hierarchy is sealed, you can resort to the Adapter pattern and purposely build a class that looks like the expected one. The following code snippet shows a sample fake class you can use to test the SimpleCalculator class in Figure 1:

 

public class FakeCalcView : BaseCalcView

 

\\{

    protected override void DisplayCore(string message)

    \\{

        return;

    \\}

\\}

Figure 2 shows a sample unit test that employs the fake calculator view in the performance of the sum. The Sum method on the SimpleCalculator class is still happy because it has an ICalcView object to work with. The provided ICalcView object, however, doesn’t require a specific window to work. In other words, the dependency is recognized, but ignored for the purpose of testing.

When you compare the code in Figures 1 and 2, you'll understand the importance of having two constructors in class SimpleCalculator. In the default constructor, the dependency is resolved in terms of a default component. The additional constructor lists all dependencies explicitly and requires that any factory code live outside the class. As you can see, this second constructor is ideal for testing but still leaves room for further generalization of the existing code. This practice brings together the need for testing and flexibility, without forcing you to employ a sometimes overkill IoC container framework to resolve dependencies.

Dealing with Mocks

A mock object is a more evolved version of a fake. A mock does all that a fake can do, and more. In a way, a mock is an object with its own personality that mimics the behavior and interface of another object. You use a mock whenever you want to test scenarios where your object interacts with its dependencies.

Like a fake, a mock is a class that implements a given interface. Mocks are ideal in situations in which the dependency represents a service with its own behavior that produces return values for subsequent steps. Mocks are significantly more complex objects to write than fakes. For this reason, ad hoc frameworks have been devised to help with mocks.

Figure 3 shows a unit test that employs a mock object to simulate an external service. The example is based on a particular mock framework—NMock2. The unit test in Listing 3 is expected to test the behavior of the TransferFunds method on the AccountService class. The method transfers a specified amount of money across two different accounts created for different currencies. Whenever the transfer occurs, the current exchange rate is provided by an external service injected into the AccountService class.

All that the AccountService class knows is the interface ICurrencyService. The mock framework just returns a dynamically created object that exposes the interface. In Listing 3 the method NewMock<T> takes care of this task. The instance returned by the mockery doesn’t contain any specific behavior yet. Desired behavior is added—exclusively for the limited purpose of the unit test—using the Expect object. The following code describes the behavior of the GetConversionRate method on the mocked ICurrencyService object:

 

Expect.Once.On(currencyService)

 

      .Method("GetConversionRate")

      .With("USD", "EUR")

      .Will(Return.Value(0.64));

 

The syntax above is equivalent to the following code:

public class MyCurrencyService : ICurrencyService

\\{

    public decimal GetConversionRate(string currencyFrom, string currencyTo)

    \\{

       if (currencyFrom

"USD" && currencyTo

"EUR") \\{
           return 0.64;
       \\}

       return 0;

    \\}

\\}

No mock frameworks come with Visual Studio 2010, but a number of them exist in the open source repository; for example, Rhino Mocks, Moq, and NMock2. Fakes and mocks address different concerns of unit testing and can be used in the same test project. However, you can also choose to use only one solution for mocking any dependencies. In this case, the best option is to use a mock framework. You can use mock frameworks to create any kind of test double, not just mocks. You also could create mock objects manually without using any mock framework.  

Effective Testing

Testability has been recognized as a fundamental attribute of software development since 1991, when the ISO/IEC 9126 paper was released. However, it's only recently that testability has been recognized as a common task to deal with in ASP.NET development. One of the reasons for this is the evolution of tools and their integration into popular development environments such as Visual Studio.

Only Visual Studio 2010 comes with improved support for testing and test-driven development. Testing, though, is a delicate art that requires, at the very minimum, a strong understanding of a few fundamental points. No software is testable in principle, but any software can be made testable with proper design. After proper design, fakes and mocks are the everyday tools you can leverage to write effective test programs.

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