This topic is discussed the class designation for unit testable. If you were started to write the unit test for your classes recently, definitely you may face the problem that some classes can’t be writing the unit test or alot of time has been spen for the single unit test. Why your class is not testable or it is not easy to write the unit test? Is you class is depending on many classes that can’t mockable?. So this topic will consolidate the patterns to implement a class that can be written unit test easily and efficiently.
Why should we write unit tests?
- Writing tests forces, you to make your code testable by refactoring the code. These topics will focus on two types of dependency-breaking refactoring below:
1. Refactoring to allow injection of fake implementations of those abstract or interfaces.
2. Abstracting concrete objects into interfaces or abstract to allow replacing underlying implementation.
- The unit tests are really just the documentation of how your code should behave. If you cannot tell from looking at the code what it is trying to do, which is a problem in itself, you at least have the unit tests to tell you the story. Because it isn’t easy to remember all the edge cases after six months?
If the answers above if not enough to convince you to start writing unit tests, then I hope the 12 reasons to write Unit Test here will satisfy you.
The recommendation of a Unit Test.
A unit test should have the following properties:
- It should be automated and repeatable.
- It should be easy to implement.
- It should be fully isolated (runs independently of other tests).
- When it fails, it should be easy to detect what was expected and determine how to pinpoint the problem.
Many people confuse the act of testing their software with the concept of a unit test. To start off, ask yourself the following questions about the tests you’ve written up to now:
- Can I run and get results from a unit test I wrote two weeks or months or years ago?
- Can any member of my team run and get results from unit tests I wrote two months ago?
- Can I run all the unit tests I’ve written in no more than a few minutes?
- Can I write a basic test in no more than a few minutes?
I. Refactoring to allow injection of fake implementations.
The helper class for the SQL connection will be implemented as a demonstration for this topic. Let’s review the code of SqlHelper below. The constructor will receive a connection string and create a SqlConnection instance with that connection string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class SqlHelper { private IDbConnection Connection { get; set; } public ConnectionState State => this.Connection.State; /// <summary> /// Create SqlHelper with connection string. /// </summary> /// <param name="connectionString">Connection String</param> public SqlHelper(string connectionString) { this.Connection = new SqlConnection(connectionString); } public void Open() => this.Connection.Open(); ... } |
The expectation, when writing the unit test for the constructor method is to create an instance of this class with a connection string and then call the Open method, the State property should be Open.
The test method can be written as below.
1 2 3 4 5 6 7 8 9 10 11 12 |
[TestMethod] public void SqlHelper_Contructor_WithReal_ConnectionString_TheOpenState_Test() { //Create SqlHelper instance with connection string. var conn = new SqlHelper("Data Source=localhost;Integrated Security=true;"); //Open the connection. conn.Open(); //Verify the State value. Assert.IsTrue(conn.State == ConnectionState.Open); } |
This test case will be passed as long as the SQL connection string is correct and code coverage is 100%. However, this test is dependent on SQL Server. So if your SQL server is not available for some reason or running this test case in CI server with different SQL Server instance. The test case will fail because it can’t connect to the real SQL Server.
Review the code above, we saw that the State property is actually returning the value of State value of SqlConnection and the Open method is called the Open method of SqlConnection. However, The purpose of this unit test is checking for SqlHelper class instead of SqlConnection.
So the unit test can be written to produce the following steps:
- Create SqlHelper with non-empty string.
- Call Open method.
- Verify that the Open method of SqlConnection is called.
- Verify the State property is Open.
- Verify that the State property of SqlConnection is called.
However, SqlHelper is creating SqlConnetion instance inside the constructor directly. So that we can’t create a mock of SqlConnection and pass into SqlHelper. Thus, We will use the Shims of MSFakes to redirect the method calling to a custom method as below. (Refer here)
The unit Test with MSFakes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[TestMethod] public void SqlHelper_Contructor_WithMsFakes_Test() { bool isOpenMethodCalled = false; bool isSatePropertCalled = false; using (ShimsContext.Create()) { //Overwrite Open method of all SqlConnection instances. ShimSqlConnection.AllInstances.Open = (SqlConnection) => { //Mark that Open method is called. isOpenMethodCalled = true; }; //Overwrite State property of all SqlConnection instances. ShimSqlConnection.AllInstances.StateGet = (SqlConnection) => { //Mark that State property is called. isSatePropertCalled = true; //Return the Connection State. return isOpenMethodCalled ? ConnectionState.Open:ConnectionState.Closed; }; var conn = new SqlHelper("Data Source=Dummy Server;"); conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); } Assert.IsTrue(isOpenMethodCalled); Assert.IsTrue(isSatePropertCalled); } |
This unit test is cover 100% of the code and not require the SQL Server as the Open method has been redirected to a fake method one. However, The MSFakes is only available in an Enterprise Visual Studio edition.
How can we write the unit test using Moq instead of MSFahes? Currently, The design of SqlHelper isn’t able to use Moq to create a mock of IDbConnection and pass it in.
Let’s refactor the SqlHelper class that can use Moq to mock the dependency objects.
1. The constructor parameter factoring.
The concept of constructor parameter factoring is asking all dependency objects at the constructor parameters of your class instead created it inside.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class SqlHelper { private IDbConnection Connection { get; set; } public ConnectionState State => this.Connection.State; /// <summary> /// Create SqlHelper with connection string. /// </summary> /// <param name="connectionString">Connection String</param> public SqlHelper(string connectionString) { this.Connection = new SqlConnection(connectionString); } /// <summary> /// Create SqlHelper with IDbConnection. /// </summary> /// <param name="connection">IDbConnection</param> public SqlHelper(IDbConnection connection) { this.Connection = connection; } public void Open() => this.Connection.Open(); } |
So the constructor method with an IDbConnection parameter was added. This method will set the IDbConnection to the Connection property. Why the parameter is IDbConnection but the required object is SqlConnection. Because Moq framework can’t mock the sealed class.
By using this constructor, we can create a mock of IDbConnection and pass into SqlHelper class. So the unit test with Moq can be written as below.
The unit tests with Moq.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
[TestMethod] public void SqlHelper_Constructor_WithMoq_Test() { var conString = "Data Source = Dummy Server;"; //Create mock of IDbConnection. var dbConnMock = new Mock<IDbConnection>(); //Mark Open() is verifiable. dbConnMock.Setup(d => d.Open()).Verifiable(); //Implement State to return ConnectionState.Open. dbConnMock.Setup(d => d.State).Returns(ConnectionState.Open); //Create SqlHelper using the second Constructor method to pass the Mock object in. var conn = new SqlHelper(dbConnMock.Object); //Execute the test. conn.Open(); //Verify the State. Assert.AreEqual(ConnectionState.Open, conn.State); //Verify the Open method call. dbConnMock.Verify(c => c.Open(), Times.Once()); //Verify the Sate property call. dbConnMock.Verify(c => c.State, Times.Once()); } |
This unit test is working as expected, but the code coverage is 73,33% only. Because constructor method with ConnectionString parameter hadn’t been tested.
One more unit test to verify constructor method with connection string of SqlHelper:
- Create an instance of SqlHelper with dummy connection string.
- Verify the Connection property is not be NULL and the type is SqlConnection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[TestMethod] public void SqlHelper_RealInstance_VerifyConnectionProperty_Test() { //Create SqlHelper with connection. var conn = new SqlHelper("Data Source=Dummy Server;"); //Create PrivateObject to get private property value. var priObj = new PrivateObject(conn); //Verify the Connection property. Assert.IsNotNull(priObj.GetProperty("Connection")); //Verify the property type. Assert.IsNotNull(priObj.GetProperty("Connection") is SqlConnection); } |
The code coverage again with both above unit tests is 100%.
2. The virtual method factoring.
The concept of virtual method factoring is all new() methods should be wrapped into virtual methods. Because the virtual method can be overwritten by the Mock Framework.
As SqlHelper above, it’s able to write the unit test with Moq and the code’s coverage is 100%. However, there is a constructor method still create a SqlConnection directly.
So a new protected virtual method named BuildConnection has been added. This method just creates and return a SqlConnection instance. Then the connection string constructor method will call BuildConnection instead of creating SqlConnection directly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class SqlHelper { private IDbConnection Connection { get; set; } public ConnectionState State => this.Connection.State; /// <summary> /// Create SqlHelper with connection string. /// </summary> /// <param name="connectionString">Connection String</param> public SqlHelper(string connectionString) { this.Connection = new SqlConnection(connectionString); } /// <summary> /// Create SqlHelper with IDbConnection. /// </summary> /// <param name="connection">IDbConnection</param> public SqlHelper(IDbConnection connection) { this.Connection = connection; } public void Open() => this.Connection.Open(); protected virtual IDbConnection BuildConnection(string connectionString) => new SqlConnection(connectionString); } |
Moreover, The new unit test method may be writen as below:
- Create the mock of SqlHelper.
- Overwrite the BuildConnection method to returns the mock of IDbConnection.
- Verify the method call.
This unit test will cover 70.59% of your code coverage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
[TestMethod] public void SqlHelper_Constructor_WithConnectionString_Test() { //Create mock of IDbConnection. var dbConnMock = new Mock<IDbConnection>(); //Mark Open() is verifiable. dbConnMock.Setup(d => d.Open()).Verifiable(); //Implement State to return ConnectionState.Open. dbConnMock.Setup(d => d.State).Returns(ConnectionState.Open); //Create the mock of SqlHelper with dummy connection string. var helperMock = new Mock<SqlHelper>("Data Source=DummyServer;"); //Orverwrite protected virtual BuildConnection method to return the mock of SqlConnection. helperMock.Protected().Setup<IDbConnection>("BuildConnection", ItExpr.IsAny<string>()) .Returns(dbConnMock.Object); var helper = helperMock.Object; //Execute the test. helper.Open(); //Verify the State. Assert.AreEqual(ConnectionState.Open, helper.State); //Verify BuildConnection method call. helperMock.Protected().Verify("BuildConnection", Times.Once(), ItExpr.IsAny<string>()); //Verify the Open method call. dbConnMock.Verify(c => c.Open(), Times.Once()); //Verify the Sate property call. dbConnMock.Verify(c => c.State, Times.Once()); } |
Conclusion
Unstill now, we have 3 unit tests using Moq framework and 1 unit test using MSFakes. That cover 100% of SqlHelper code coverage.
Moq
- SqlHelper_RealInstance_VerifyConnectionProperty_Test.
- SqlHelper_Constructor_WithMockIDbConnection_Test.
- SqlHelper_Constructor_WithConnectionString_Test.
MSFakes
- SqlHelper_Contructor_WithMsFakes_Test.
II. Abstracting concrete objects into interfaces or abstract class.
This pattern is recommended that your classes should be inherited an interface or abstract class. By this way, the mock framework can create a fake object of your class from derived interface or abstract class.
The SqlHelper above will be extracted to an interface as following. All necessary methods and properties are defined in the interface.
The interface.
1 2 3 4 5 6 7 8 |
//The interface of SqlHelper class. public interface ISqlHelper { ConnectionState State { get; } void Open(); } |
The SqlHelper class.
1 2 3 4 5 6 |
public class SqlHelper : ISqlHelper { ... } |
Download source code here.
Agree that coding the Unit test is quite challenging. However, If you want to keep the code coverage around 70% is not too hard. I have some HBD projects here that have the code coverage around 70% you would take a look.
Hope this topic help when you’re going to implement your application intended for unit testing purpose.