Test Double is a generic term for any case where you replace a production object for testing purposes.
We now introduce a significant piece of the unit testing approach—test doubles. Using test doubles is a way of creating a class whose behavior can be changed at runtime. These classes can be used to unit test our code much more thoroughly than we could have otherwise. Let’s look at the details of how these classes help write unit tests.
How Do Test Doubles Work?
Test doubles work by replacing real classes with an imitation class that can be created at runtime, not design time. Using this imitation class, we can change the behavior to perform any action or return any values that we want for a test. In this way, we can simulate many different situations happening to the business logic without having to go to the trouble to set up the actual circumstances.
Note
A requirement for test doubles is to use interfaces. It is possible to create a test double for a class without an interface, but it severely limits the capabilities of the double and the test. Interfaces are used in many other ways in this book, so the lesson to be learned is to put interfaces on all of your services and many other classes.
What Need Do Test Doubles Satisfy?
A good example of a place where doubles are useful is when implementing a repository.
The Repository Pattern is used quite often because it removes the tight coupling with a particular database, allowing easier development and easy replacement of the backend data store.
The code in abocve is a typical example of a Repository Pattern. Our business logic uses the repository to retrieve a user from the database, make a change to it, and then save it back to the database.
using System;
using System.Data.SqlClient;
using Microsoft.Practices.Unity;
namespace CodeSamples.Ch05_ClassMocking.Listing01
{
public class MockingExample
{
public void UpdateUserData()
{
var repo = ServiceLocator.Resolve
var user = repo.GetUserData(1);
user.Name = “New Name”;
repo.SaveUserData(user);
}
}
public class User
{
public int Id { get; internal set; }
public string Name { get; set; }
}
public interface IRepository
{
User GetUserData(int pUserId);
void SaveUserData(User pUser);
}
public class Repository : IRepository
{
private string _connectionString = “connStringGoesHere”;
public User GetUserData(int pUserId)
{
var qryString = “select * from User where id= @id”;
var conn = new SqlConnection(_connectionString);
var cmd = new SqlCommand(qryString, conn);
cmd.Parameters.AddWithValue(“@id”, pUserId);
try
{
conn.Open();
var reader = cmd.ExecuteReader();
reader.Read();
var usr = new User
{
Id = (int)reader[0],
Name = reader[1].ToString()
};
reader.Close();
return usr;
}
catch (Exception)
{
//do something with the exception
}
return null;
}
public void SaveUserData(User pUser)
{
var qryString = “Update user set name = @username where id= @id”;
var conn = new SqlConnection(_connectionString);
var cmd = new SqlCommand(qryString, conn);
cmd.Parameters.AddWithValue(“@id”, pUser.Id);
cmd.Parameters.AddWithValue(“@username”, pUser.Name);
try
{
conn.Open();
cmd.ExecuteNonQuery();
}
catch (Exception)
{
//do something with the exception
}
}
}
public static class ServiceLocator
{
private static UnityContainer _container;
static ServiceLocator()
{
_container = new UnityContainer();
_container.RegisterInstance
_container.RegisterType
}
public static TProxyType Resolve
{
return _container.Resolve
}
}
If we wanted to write a unit test for the UpdateUserData method, it would look like
[TestClass]
public class MockingExampleTestClass
{
[TestMethod]
public void UpdateUserData_WhenCalled_SetsNewName()
{
var mockExample = new MockingExample();
mockExample.UpdateUserData();
var repo = ServiceLocator.Resolve
var user = repo.GetUserData(1);
Assert.AreEqual(“New Name”, user.Name);
}
}
The important thing to notice in this test is that the user information comes from the database and is saved back to the database. Even the test must access the database to assert that the proper changes have been made. When a live database is accessed, it changes a unit test into an integration test and also introduces a lot of dependencies, not to mention execution time. This one simple test now makes an active and up-to-date database required for a successful run. This is how tests become brittle.
Creating a Stub
To break the dependency on the database, we write a test double for the repository. This particular type of test double is called a stub. Above is showed an example of a stub and how one is used.
(Obivously the test code and business logic in the same code file is for brevity :-) )
using System;
using System.Data.SqlClient;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CodeSamples.Ch05_ClassMocking.Listing03
{
public class MockingExample
{
public void UpdateUserData()
{
var repo = ServiceLocator.Resolve
var user = repo.GetUserData(1);
user.Name = “New Name”;
repo.SaveUserData(user);
}
}
public class User
{
public int Id { get; internal set; }
public string Name { get; set; }
public DateTime LastUpdateDate { get; set; }
public DateTime CreateDate { get; set; }
public User()
{
CreateDate = DateTime.Now;
LastUpdateDate = DateTime.Now;
}
}
public interface IRepository
{
User GetUserData(int pUserId);
void SaveUserData(User pUser);
}
public class Repository : IRepository
{
private string _connectionString = “connStringGoesHere”;
public User GetUserData(int pUserId)
{
var qryString = “select * from User where id= @id”;
var conn = new SqlConnection(_connectionString);
var cmd = new SqlCommand(qryString, conn);
cmd.Parameters.AddWithValue(“@id”, pUserId);
try
{
conn.Open();
var reader = cmd.ExecuteReader();
reader.Read();
var usr = new User
{
Id = (int)reader[0],
Name = reader[1].ToString(),
CreateDate = DateTime.Parse(reader[2].ToString()),
LastUpdateDate = DateTime.Parse(reader[3].ToString())
};
reader.Close();
return usr;
}
catch (Exception)
{
//do something with the exception
}
return null;
}
public void SaveUserData(User pUser)
{
var qryString = “Update user set name = @username where id= @id”;
var conn = new SqlConnection(_connectionString);
var cmd = new SqlCommand(qryString, conn);
pUser.LastUpdateDate = DateTime.Now;
cmd.Parameters.AddWithValue(“@id”, pUser.Id);
cmd.Parameters.AddWithValue(“@username”, pUser.Name);
cmd.Parameters.AddWithValue(“@CreateDate”, pUser.CreateDate);
cmd.Parameters.AddWithValue(“@LastUpdateDate”, pUser.LastUpdateDate);
try
{
conn.Open();
cmd.ExecuteNonQuery();
}
catch (Exception)
{
//do something with the exception
}
}
}
public class RepositoryStub : IRepository
{
public User StubUser{get;set;}
public User GetUserData(int pUserId)
{
//Replace code to read from the database
//and just return a new User.
if (StubUser == null)
{
StubUser = new User();
StubUser.Id = 1;
StubUser.Name = “Brad Irby”;
}
return StubUser;
}
public void SaveUserData(User pUser)
{
//do nothing in this stub
}
}
public static class ServiceLocator
{
private static UnityContainer _container;
static ServiceLocator()
{
_container = new UnityContainer();
_container.RegisterInstance
_container.RegisterType
}
public static TProxyType Resolve
{
return _container.Resolve
}
}
[TestClass]
public class MockingExampleTestClass
{
[TestMethod]
public void UpdateUserData_WhenCalled_SetsNewName()
{
//Arrange
var container = ServiceLocator.Resolve
var stubRepo = new RepositoryStub();
container.RegisterInstance
var mockExample = new MockingExample();
//Act
mockExample.UpdateUserData();
//Assert
var repo = ServiceLocator.Resolve
var user = repo.GetUserData(1);
Assert.AreEqual(“New Name”, user.Name);
}
}
}
In this listing, notice that two classes implement IRepository—the real repository and the RepositoryStub. By implementing that interface, the RepositoryStub can be used as a replacement for the real Repository. Our stub, however, is hardcoded to return a User value without accessing the database, breaking the dependency on the database.
To use the new RepositoryStub, we need to replace the real repository in the ServiceLocator. To do that, we get a copy of the Unity container that the service locator uses and we register an instance of the RepositoryStub as the appropriate class to return when calling code requests an IRepository. Now, if any class anywhere in the system requests an IRepository from the ServiceLocator, it receives a copy of our stub IRepository instead of a real repository.
Note
Some Dependency Injection containers do not replace an existing registration with a new one. We use Unity, which behaves appropriately for this example, but if you use a different container, you must replace the logic with something sufficient to replace the existing IRepository registration.
This stub approach works for this one test, but if we write another test that operates in a different way, we are faced with writing another stub that does something different. What we need is a stub whose behavior can be modified at runtime to suit the needs of a particular test. To do this, we turn to our mocking library, Moq.
Distinguishing Between Mocks and Stubs
Before we go to further, we need to distinguish a difference between two types of test doubles (mocks and stubs). Mocks and stubs are both used extensively to replace services for unit testing and are similar in the way they are created and behave. The difference lies in the functionality they replace.
Mocks are used for behavior verification. In other words, if you need to verify that a particular method was called a certain number of times, use a mock. Mocks also verify that methods are called in a certain order or not at all.
Stubs are used to replace behavior in a service. If a method needs to return a specific value when called during a test, use a stub. If you need to ensure that a method is called with the correct parameters, use a stub.
Let’s take the current example with the repository. To verify that the GetUserData and SaveUserData are called once each and in that order, use a mock. To replace the GetUserData method to return some predictable data, use a stub.
Fortunately for us, the technical distinction between mocks and stubs is insignificant because the mocking framework we use can create objects that perform both mocking and stubbing activities. When creating a test double, we can count the number of times a method is called and also replace a method to perform some activity, all in the same object.
Our library (Moq) refers to the objects it creates as mocks, but keep in mind that these objects can be used as either mocks or stubs. For the remainder of this book, referring to a mock, we mean a class generated by Moq, not a class that is used exclusively for behavior verification.
On next article we’ll crate a Mock.
Getting back to the example, using the same repository and test that we outlined previously, we now convert that test to use a mock created by Moq instead of the repository stub.
Stai tuned!
References:
Reengineering .NET
By: Bradley Irby