Recently I came across code like below in UI (code behind) which basically calls a static method on a repository :-
RandomPage.aspx.cs :-
protected void btnSave_Click(object sender, EventArgs e) { Guid id = Guid.NewId(); string name = txtName.Text; RandomRepository.RandomStaticMethod(id, name); }
Now let us look at the “RandomStaticMethod()” method in the “RandomRepository”
class. The method just calls couple of another static methods in the “DataAccess” class.
RandomRepository.cs :-
/// <summary> /// Repository for some random data access /// </summary> public class RandomRepository { /// <summary> /// Name of the stored procedure to insert data in a table /// </summary> private const string SomeRandomTableInsertSP = "SomeRandomTable_Insert"; /// <summary> /// The connection string /// </summary> private const string ConnectionString = "Random connection string"; #region Static method /// <summary> /// A random static "Factory" method /// </summary> /// <param name="id">The value for the Id parameter.</param> /// <param name="name">The value for the Name parameter.</param> public static void RandomRepositoryMethod(Guid id, string name) { DataAccess.ExecuteNonQuery( ConnectionString, SomeRandomTableInsertSP, DataAccess.CreateParameter("@Id", SqlDbType.UniqueIdentifier, id), DataAccess.CreateParameter("@Name", SqlDbType.NVarChar, name)); } #endregion }
DataAccess.cs:-
/// <summary> /// Classes for the database operations /// </summary> public class DataAccess { /// <summary> /// Creates the parameter. /// </summary> /// <param name="name">The name of the parameter.</param> /// <param name="type">The type of the parameter.</param> /// <param name="value">The value of the parameter.</param> /// <returns>The SqlParameter</returns> public static SqlParameter CreateParameter(string name, SqlDbType type, object value) { return new SqlParameter { ParameterName = name, SqlDbType = type, Value = value }; } /// <summary> /// Executes the query. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="storedProcedure">The stored procedure.</param> /// <param name="inParameters">The in parameters.</param> public static void ExecuteNonQuery(string connectionString, string storedProcedure, params DbParameter[] inParameters) { // Do some actual database operations here } }
What do we need to test?
– RandomRepositoryMethod().
We need to test If It could call DataAccess.ExecuteNonQuery() with appropriate number/type/values of parameters.
Why? In this example, I have only two parameters. However, in real world, you might be using a lot more than that. I have seen about 20 parameters being passed and many time have made mistakes by copying pasting the same parameters and getting the error later. If you say you never made that mistake, you must be lying.
Now, read on…
If you look at RandomRepositoryMethod() you see that the RandomRepository class is dependent on the DataAccess class which does the actual database operations. Since this is a unit test for RandomRepositoryMethod() and not for DataAccess and we don’t want to interact with the actual database, we need to “mock” the DataAccess class.
How to mock the DataAccess here?
- Add an interface “IDataAccess” which has the dependency methods – CreateParameter and ExecuteNonQuery.
- Explicitly implement the IDataAccess methods – CreateParameter() and ExecuteNonQuery() on the DataAccess class.
- The explicitly implemented interface methods – CreateParameter() and ExecuteNonQuery() should just call their static counter parts.
- Inject the dependency (DataAccess) to the dependent method.
- Write the unit test method using Moq.
IDataAccess interface :-
/// <summary> /// An interface for DataAccess /// </summary> public interface IDataAccess { /// <summary> /// Creates a SqlParameter with the given name, type, and value. /// </summary> /// <param name="name">The name of the parameter.</param> /// <param name="type">The type of the parameter.</param> /// <param name="value">The value of the parameter.</param> /// <returns>Returns a SqlParameter created with the given arguments.</returns> SqlParameter CreateParameter(string name, SqlDbType type, object value); /// <summary> /// Executes the given stored procedure. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="storedProcedure">The stored procedure.</param> /// <param name="inParameters">The parameters to pass into the stored procedure.</param> void ExecuteNonQuery(string connectionString, string storedProcedure, params DbParameter[] inParameters); }
Explicitly implementing the interface
Explicitly implemented methods are in line 35 and 47 which just call their static counterparts.
/// <summary> /// Classes for the database operations /// </summary> public class DataAccess : IDataAccess { /// <summary> /// Creates the parameter. /// </summary> /// <param name="name">The name of the parameter.</param> /// <param name="type">The type of the parameter.</param> /// <param name="value">The value of the parameter.</param> /// <returns>The SqlParameter</returns> public static SqlParameter CreateParameter(string name, SqlDbType type, object value) { return new SqlParameter { ParameterName = name, SqlDbType = type, Value = value }; } /// <summary> /// Executes the query. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="storedProcedure">The stored procedure.</param> /// <param name="inParameters">The in parameters.</param> public static void ExecuteNonQuery(string connectionString, string storedProcedure, params DbParameter[] inParameters) { // Do some database operation here } /// <summary> /// Calls the static ExecuteNonQuery() method /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="storedProcedure">The stored procedure.</param> /// <param name="inParameters">The in parameters.</param> void IDataAccess.ExecuteNonQuery(string connectionString, string storedProcedure, params DbParameter[] inParameters) { DataAccess.ExecuteNonQuery(connectionString, storedProcedure, inParameters); } /// <summary> /// Calls the static CreateParameter() method /// </summary> /// <param name="name">The name of the parameter.</param> /// <param name="type">The type of the parameter.</param> /// <param name="value">The value of the parameter.</param> /// <returns>The SqlParameter</returns> SqlParameter IDataAccess.CreateParameter(string name, SqlDbType type, object value) { return DataAccess.CreateParameter(name, type, value); } }
Inject the dependency in the dependent method (Method injection)
Modify the dependent method to accept a parameter of type IDataAccess. You need to pass an instance of DataAccess from your UI code too.
/// <summary> /// A random static "Factory" method /// </summary> /// <param name="id">The value for the Id parameter.</param> /// <param name="name">The value for the Name parameter.</param> /// <param name="dataAccess">The data access.</param> public static void RandomRepositoryMethod(Guid id, string name, IDataAccess dataAccess) { dataAccess.ExecuteNonQuery( ConnectionString, SomeRandomTableInsertSP, DataAccess.CreateParameter("@Id", SqlDbType.UniqueIdentifier, id), DataAccess.CreateParameter("@Name", SqlDbType.NVarChar, name)); }
Write the unit test method :-
/// <summary> ///A test for RandomRepositoryMethod ///</summary> [TestMethod()] public void RandomRepositoryMethodTest() { Guid idValue = new Guid("73592249-AD57-4CDF-B5FC-9C30F65C2376"); string nameValue = "test"; var dataAccess = new Mock<IDataAccess>(); IDataAccess actualDataAccess = new DataAccess(); dataAccess.Setup(a => a.CreateParameter(It.IsAny<string>(), It.IsAny<SqlDbType>(), It.IsAny<object>())) .Returns<string, SqlDbType, object>((name, type, value) => actualDataAccess.CreateParameter(name, type, value)); RandomRepository.RandomRepositoryMethod(idValue, nameValue, dataAccess.Object); dataAccess.Verify( d => d.ExecuteNonQuery(It.IsAny<string>(), "SomeRandomTable_Insert", new DbParameter[]{ It.Is<SqlParameter>(p=>p.ParameterName == "@Id" && p.SqlDbType == SqlDbType.UniqueIdentifier && (Guid)p.Value == idValue), It.Is<SqlParameter>(p=>p.ParameterName == "@Name" && p.SqlDbType == SqlDbType.NVarChar && (string)p.Value == nameValue)} ), Times.Once()); }
Lines 07 and 08 set up the value for the “Id” and “Name” parameters.
Line 09 creates a mock of the IDataAccess interface.
Line 10 creates an instance of real DataAccess class.
Why do we need an instance of DataAccess class? – Because, the ExecuteNonQuery() calls CreateParameter() to return an instance of SqlParameter. We need to set that up to return a SqlParameter If we call CreateParameter() with “any” string parameter, “any” SqlDbType and “any” object value. That’s what line 11 and 12 (they are actually one statement) are doing.
Line 14 – Calls the static method.
Lines 15 – 19 (all lines are actually one statement)
Verifies that dataAccess.ExecuteNonQuery was called with “any” string (any connection string), the required stored procedure (“SomeRandomTable_Insert”) and
two SqlParameters – first having parameter name “@Id”, type SqlDbType.UniqueIdentifier and of value “73592249-AD57-4CDF-B5FC-C30F65C2376” AND second having parameter name “@Name”, type SqlDbType.NVarChar and of value “test”. It also verifies that the DataAccess.ExecuteNonQuery() was called exactly one time.
That’s it – you have unit tested your static method. There is one caveat – you need to pass the DataAccess object to each method you want to test (Method Injection). You couldn’t pass the dataAccess object to the class’s constructor because then that instance of dataAccess can not be accessed in the static method. It won’t compile.
public class RandomRepository { /// <summary> /// DataAccess object /// </summary> IDataAccess dataAccess = null; /// <summary> /// Initializes a new instance of the <see cref="RandomRepository" /> class. /// </summary> /// <param name="dataAccess">The data access.</param> public RandomRepository(IDataAccess dataAccess) { this.dataAccess = dataAccess; } /// <summary> /// A random static "Factory" method /// </summary> /// <param name="id">The value for the Id parameter.</param> /// <param name="name">The value for the Name parameter.</param> public static void RandomRepositoryMethod(Guid id, string name) { // Won't compile - "An object reference is required to access non-static member" dataAccess.ExecuteNonQuery( ConnectionString, SomeRandomTableInsertSP, DataAccess.CreateParameter("@Id", SqlDbType.UniqueIdentifier, id), DataAccess.CreateParameter("@Name", SqlDbType.NVarChar, name)); } }
Personally, I didn’t like the ExecuteNonQuery() and CreateParameter() being static. Sure, static methods have their own advantages. My personal preference is to limit them in the utility classes because I find them difficult (read “less easy”) to unit test. I just wanted to mention that performance difference between static method and instance method is negligible.
From http://msdn.microsoft.com/en-us/library/79b3xss3.aspx
A call to a static method generates a call instruction in Microsoft intermediate language (MSIL), whereas a call to an instance method generates a callvirt instruction, which also checks for a null object references. However, most of the time the performance difference between the two is not significant.