Unit Testing - A Sample Approach

Image

Any explanation of Unit Testing is incomplete without a definition of a 'Unit' up front.A unit can be defined as the smallest piece of code that ideally discharges a single responsibility, is aware of what is needed by it to perform that responsibility, is aware of the output it generates and finally is aware of the other units of code that it is dependent on to successfully perform its responsibility. An added attribute of a Unit is its ability to be tested in isolation.In this blog I will look at how one can develop unit tests for units of code that are aware of their responsibility, inputs, outputs and dependencies. In order to gain an overview of how one can write such methods please read my earlier blog - Dependency Injection - A Foundation.We will also look at the role of mocking in providing the necessary isolation for the unit being tested. MoQ is a popular framework for supplying fully constructed objects that a unit is dependent on to the unit. It can be obtained for local use from here. References can be found at the bottom of the blog.

Unit Testing and its Aim

A good definition of unit testing is as outlined by Roy Osherove: A unit test is an automated piece of code that invokes a unit of work in the system and then checks a single assumption about the behavior of that unit of work.The aim of unit testing is to gather a set of assumptions and make sure that in all cases the unit of work returns the expected result. If the unit of work does not return the expected result the unit test has failed. The important thing to note here is that the set of assumptions should include scenarios that test all branches of logic within the unit of work and also how the unit of work would react in case illegal or empty input parameters are provided to it.

Setting up Unit Testing

Let's continue to look at the set of interfaces, classes and methods that we developed for Dependency Injection - A Foundation. The first step in setting up unit testing is to add a unit testing project to your solution - in Visual Studio it is available as a template and can be added as shown below.

Adding_a_Unit_Testing_Project_in_Visual_Studio

Additional references will need to be added to MoQ and to Castle.Core which is the DI framework internally used by MoQ. Another important reference that you will need to add is the assembly that holds all the units of work that you wish to unit test.

Simple unit tests for units that do not depend on other units

In my previous code samples an example of a unit of work that is aware of its inputs and output and does not depend on other units of work would be the method "CalculateOilChangeCost" in the class "OilChangeCostCalculator". This class implements the interface "ICalculateOilChangeCost". I am reproducing the class here so that you can readily review the logic:[sourcecode language="csharp"]public class OilChangeCostCalculator : ICalculateOilChangeCost{ public double CalculateOilChangeCost(DateTime lastServiceDate) { var oilChangeCost = 0;// Validations if (lastServiceDate > DateTime.Now) { // -1 denotes an error: Last Service Date cannot be in the future return -1; }// If the Last Service Date was less than 90 days ago then an Oil Change is $0 // If the Last Service Date was more than or equal to 90 days ago but less than 180 days ago then an Oil Change is $25 // If the Last Service Date was more than or equal to 180 days ago then an Oil Change is $50 if ((DateTime.Now - lastServiceDate).TotalDays >= 90 && (DateTime.Now - lastServiceDate).TotalDays < 180) { oilChangeCost = 25; }if ((DateTime.Now - lastServiceDate).TotalDays >= 180) { oilChangeCost = 50; }return oilChangeCost; }}[/sourcecode]The method "CalculateOilChangeCost" performs a single responsibility of determining the cost of an oil change. It takes in the last servicing date and returns the cost of oil change. As can be seen the logic checks to see if the servicing date is in the future, and if it is, the method returns an error code (one could also throw a custom exception here). If the servicing date is appropriate then depending on how long ago the vehicle was serviced a cost for oil change is calculated.What would be an initial set of assumptions to be able to test this unit of work? Given the layout of the logic, a good set of assumptions that would test all lines of code would be as follows:

  • Error is returned when last service date is in the future
  • Oil change cost is 0 when vehicle has been serviced between 0 and 89 days ago
  • Oil change cost is 25 when vehicle has been serviced between 90 and 179 days ago
  • Oil change cost is 50 when vehicle has been serviced more than 180 days ago

To be able to write unit tests for the assumptions above you will need to introduce a "Unit Test Class" to your unit test project - in this case I have added the class "OilChangeCostCalculatorTests". This class is decorated by the attribute "[TestClass]" and each of the test within are decorated by the attribute "[TestMethod]". A test method in this simple scenario would be instantiating the class and calling the unit of work within it by passing a servicing date and then asserting whether the returned value matches the assumption for which the test was written. The code below shows the tests written for all 4 of the assumptions above.[sourcecode language="csharp"][TestClass]public class OilChangeCostCalculatorTests{[TestMethod]public void Error_Is_Returned_When_Last_Service_Date_Is_In_The_Future(){OilChangeCostCalculator oilChangeCostCalc = new OilChangeCostCalculator();var oilChangeCost = oilChangeCostCalc.CalculateOilChangeCost(new DateTime(2016, 09, 08));Assert.IsTrue(oilChangeCost == -1);}[TestMethod]public void Oil_Change_Cost_Is_0_When_Vehicle_Has_Been_Serviced_Between_0_And_89_Days_Ago(){OilChangeCostCalculator oilChangeCostCalc = new OilChangeCostCalculator();var oilChangeCost = oilChangeCostCalc.CalculateOilChangeCost(new DateTime(2016, 08, 01));Assert.IsTrue(oilChangeCost == 0);}[TestMethod]public void Oil_Change_Cost_Is_25_When_Vehicle_Has_Been_Serviced_Between_90_And_179_Days_Ago(){OilChangeCostCalculator oilChangeCostCalc = new OilChangeCostCalculator();var oilChangeCost = oilChangeCostCalc.CalculateOilChangeCost(new DateTime(2016, 03, 01));Assert.IsTrue(oilChangeCost == 25);}[TestMethod]public void Oil_Change_Cost_Is_50_When_Vehicle_Has_Been_Serviced_More_Than_180_Days_Ago(){OilChangeCostCalculator oilChangeCostCalc = new OilChangeCostCalculator();var oilChangeCost = oilChangeCostCalc.CalculateOilChangeCost(new DateTime(2016, 01, 01));Assert.IsTrue(oilChangeCost == 50);}}[/sourcecode]Visual Studio allows one to run and debug all the tests within a test class. By simply right-clicking anywhere withing the class editor one gets the option to "Run Tests" which then displays the output of the tests in question in the "Test Explorer" window as shown below. In this case all 4 tests matched the assumptions that they were written for and hence passed.

Running_Unit_Tests_in_Visual_Studio

Unit tests for units that depend on other units

Mocking

One of the concepts I discussed earlier in the blog was that of a unit having the ability of being tested in isolation. A simple example of an isolated unit would be the method "CalculateOilChangeCost" in the class "OilChangeCostCalculator" that we just saw in the last section. It is isolated by default because it does not depend on any other unit of work. However, this is seldom the case. More often then not one encounters units that are dependent on several other units for functioning. How can such units be isolated from their dependencies? The answer lies in the unit of work not being flustered about its dependencies and if they would function correctly in a given situation - the test is for the unit itself and not its dependencies.A paradigm that allows a unit test writer to keep the unit isolated is supplying the unit with constructed units of work that the unit is dependent on. In a way, telling the unit "Dont worry about the dependencies and whether they are accurate for this assumption. Just take what I give you and assume it is right and tell me what would your output be for this input.". This supply of constructed units of work while testing the unit in isolation is done when other units of work are not functioning as part of an up and running application - something that makes it necessary to mock (or fake) the dependencies. One of the fundamental concepts that makes this possible is Inversion or Control or having the unit that is being tested not depend on the concrete instances of the dependencies but having it be dependent on a contract that the dependencies must abide by. In this way any instance of the dependency could be supplied to the unit as long as it abides by the contract. It could be a fake instance too - constructed precisely to suit the assumption the unit is being tested for.As indicated earlier, MoQ is a popular framework that allows one to create and supply mock instances of dependencies to units. It also allows one to do some other interesting things to ensure code coverage is as comprehensive as one chooses it to be. We will see some of this in the next section.

A unit that depends on other units

In previous code samples an example of a unit that is dependent on other units would be the method "CalculateServiceCost" in the class "ServiceCostCalculator". The class "ServiceCostCalculator" implements the interface "ICalculateServiceCost". I am reproducing it here for a quick review of the logic.[sourcecode language="csharp"]public class ServiceCostCalculator : ICalculateServiceCost{ #region Interfaces the class is dependent on private readonly ICheckIfOilChangeIsRequired _checkIfOilChangeIsRequired; private readonly ICalculateOilChangeCost _calculateOilChangeCost; #endregionpublic ServiceCostCalculator( ICheckIfOilChangeIsRequired checkIfOilChangeIsRequired, ICalculateOilChangeCost calculateOilChangeCost) { _checkIfOilChangeIsRequired = checkIfOilChangeIsRequired; _calculateOilChangeCost = calculateOilChangeCost; }public double CalculateServiceCost(int year, DateTime lastServiceDate) { // Validations if (lastServiceDate > DateTime.Now) { // -1 denotes an error: Last Service Date cannot be in the future return -1; }// The Standard Cost of Service is $35 // If the Vehicle is more than 5 years old add another $35 // Check if an Oil change is required - if yes add the amount returned by the Oil Change Cost calculator double vehicleServiceCharge = 35;// Get the year from the current date var currentYear = DateTime.Now.Year;if (currentYear - year > 5) { vehicleServiceCharge += 35; }if (_checkIfOilChangeIsRequired.CheckIfOilChangeIsRequired(lastServiceDate)) { var oilChangeCost = _calculateOilChangeCost.CalculateOilChangeCost(lastServiceDate); if (oilChangeCost != -1) { vehicleServiceCharge += oilChangeCost; } else { return oilChangeCost; } }return vehicleServiceCharge; }}[/sourcecode]As can be seen, the unit calculates the cost of servicing a vehicle but depends on other units to tell it if an oil change is required and what the cost of that oil change would be.What would be an initial set of assumptions to be able to test this unit of work? Given the layout of the logic, a sampling of assumptions that would be as follows:

  • Error Is Returned When Last Service Date Is In The Future
  • AppropriateCost Is Returned When Vehicle Is More Than 5 Years Old Serviced Less Than 90 DaysAgo
  • AppropriateCost Is Returned When Vehicle Is More Than 5 Years Old Serviced More Than 90 Days Ago But Less Than 180 Days Ago
  • AppropriateCost Is Returned When Vehicle Is More Than 5 Years Old Serviced More Than 180 Days Ago

The first assumption can be unit tested by writing a test as follows[sourcecode language="csharp"][TestMethod]public void Error_Is_Returned_When_Last_Service_Date_Is_In_The_Future(){ // Arrange all dependencies that are required by the ServiceCostCalculator class's CalculateServiceCost method Mock&lt;ICheckIfOilChangeIsRequired&gt; mockOilChangeChecker = new Mock&lt;ICheckIfOilChangeIsRequired&gt;(); Mock&lt;ICalculateOilChangeCost&gt; mockOilChangeCostCalculator = new Mock&lt;ICalculateOilChangeCost&gt;();ServiceCostCalculator serviceCostCalc = new ServiceCostCalculator(mockOilChangeChecker.Object, mockOilChangeCostCalculator.Object);// Act - Call the ServiceCostCalculator class's CalculateServiceCost method // Pass in the Last Date of Service as a future date and assert that the return value is -1 var serviceCost = serviceCostCalc.CalculateServiceCost(2006, new DateTime(2016, 09, 10)); // Assert whether the outcome Assert.IsTrue(serviceCost == -1);}[/sourcecode]There are 2 salient points to note here:1) MoQ allows one to create mock or fake objects that abide by a certain contract or interface. 'mockOilChangeChecker' and 'mockOilChangeCostCalculator' are the 2 mock objects that need to be constructed for the unit being tested to work. They abide by the interfaces 'ICheckIfOilChangeIsRequired' and 'ICalculateOilChangeCost' respectively.2) The 'Object' property on the mock instance allows one access to the concrete object that needs to be passed on to the unit being tested.The second assumption can be unit tested by writing a test as follows[sourcecode language="csharp"][TestMethod]public void AppropriateCost_Is_Returned_When_Vehicle_Is_More_Than_5_Years_Old_Serviced_Less_Than_90_Days_Ago(){// Arrange all dependencies that are required by the ServiceCostCalculator class's CalculateServiceCost methodMock<ICheckIfOilChangeIsRequired> mockOilChangeChecker = new Mock<ICheckIfOilChangeIsRequired>();Mock<ICalculateOilChangeCost> mockOilChangeCostCalculator = new Mock<ICalculateOilChangeCost>();ServiceCostCalculator serviceCostCalc = new ServiceCostCalculator(mockOilChangeChecker.Object, mockOilChangeCostCalculator.Object);// Act - Call the ServiceCostCalculator class's CalculateServiceCost method// Pass in the Vehicle Year as 2006 and Last Service Date as 2016-06-08 and assert that the Service Cost is $70var serviceCost = serviceCostCalc.CalculateServiceCost(2006, new DateTime(2016, 06, 08));// Verify that the CheckIfOilChangeIsRequired method was called once on the Mock object of ICheckIfOilChangeIsRequired with the specified DateTime passed inmockOilChangeChecker.Verify(oilChangeChecker => oilChangeChecker.CheckIfOilChangeIsRequired(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 06 && dt.Day == 08))), Times.Once);// Verify that the CalculateOilChangeCost method was not called on the Mock object of the ICalculateOilChangeCost with the specified DateTime passed in// This is because in the test the vehicle was serviced less than 90 days agomockOilChangeCostCalculator.Verify(oilChangeCostCalculator => oilChangeCostCalculator.CalculateOilChangeCost(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 06 && dt.Day == 08))), Times.Never);// Assert whether the outcomeAssert.IsTrue(serviceCost == 70);}[/sourcecode]The salient points to note here are:1) We can verify whether the unit being tested actually called on a certain method on its dependency. For example, the test verifies whether the method 'CheckIfOilChangeIsRequired' was called on mockOilChangeChecker.Object.2) We can verify that the parameters passed on to the inner method are what we expected to be passed.3) We can write the test to verify the number of times the inner method was called4) All these verifications must pass in order for the test to pass.The lines of code specific to these are[sourcecode language="csharp"]mockOilChangeChecker.Verify(oilChangeChecker => oilChangeChecker.CheckIfOilChangeIsRequired(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 06 && dt.Day == 08))), Times.Once);mockOilChangeCostCalculator.Verify(oilChangeCostCalculator => oilChangeCostCalculator.CalculateOilChangeCost(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 06 && dt.Day == 08))), Times.Never);[/sourcecode]The third assumption can be unit tested by writing a test as follows[sourcecode language="csharp"][TestMethod]public void AppropriateCost_Is_Returned_When_Vehicle_Is_More_Than_5_Years_Old_Serviced_More_Than_90_Days_Ago_But_Less_Than_180_Days_Ago(){// Arrange all dependencies that are required by the ServiceCostCalculator class's CalculateServiceCost methodMock<ICheckIfOilChangeIsRequired> mockOilChangeChecker = new Mock<ICheckIfOilChangeIsRequired>();Mock<ICalculateOilChangeCost> mockOilChangeCostCalculator = new Mock<ICalculateOilChangeCost>();ServiceCostCalculator serviceCostCalc = new ServiceCostCalculator(mockOilChangeChecker.Object, mockOilChangeCostCalculator.Object);// Set up the Mocks to return appropriate values in accordance with the tests// The Mocks need to be set up to return the appropriate values in accordance with the tests as the logic being unit tested is not for the mocked dependenciesmockOilChangeChecker.Setup(oilChangeChecker => oilChangeChecker.CheckIfOilChangeIsRequired(It.IsAny<DateTime>())).Returns(true);mockOilChangeCostCalculator.Setup(oilChangeCostCalculator => oilChangeCostCalculator.CalculateOilChangeCost(It.IsAny<DateTime>())).Returns(25);// Act - Call the ServiceCostCalculator class's CalculateServiceCost method// Pass in the Vehicle Year as 2006 and Last Service Date as 2016-03-08 and assert that the Service Cost is $95var serviceCost = serviceCostCalc.CalculateServiceCost(2006, new DateTime(2016, 03, 08));// Verify that the CheckIfOilChangeIsRequired method was called once on the Mock object of ICheckIfOilChangeIsRequired with the specified DateTime passed inmockOilChangeChecker.Verify(oilChangeChecker => oilChangeChecker.CheckIfOilChangeIsRequired(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 03 && dt.Day == 08))), Times.Once);// Verify that the CalculateOilChangeCost method was called once on the Mock object of the ICalculateOilChangeCost with the specified DateTime passed inmockOilChangeCostCalculator.Verify(oilChangeCostCalculator => oilChangeCostCalculator.CalculateOilChangeCost(It.Is<DateTime>(dt => (dt.Year == 2016 && dt.Month == 03 && dt.Day == 08))), Times.Once);// Assert whether the outcomeAssert.IsTrue(serviceCost == 95);}[/sourcecode]The salient point to note here is1) We have used the MoQ ability to ensure that the mock objects that are being passed to the unit being tested return set values that are in accordance with our assumption for the test.The lines of code specific to these are[sourcecode language="csharp"]mockOilChangeChecker.Setup(oilChangeChecker => oilChangeChecker.CheckIfOilChangeIsRequired(It.IsAny<DateTime>())).Returns(true);mockOilChangeCostCalculator.Setup(oilChangeCostCalculator => oilChangeCostCalculator.CalculateOilChangeCost(It.IsAny<DateTime>())).Returns(25);[/sourcecode]

Conclusion

As we have seen, a combination of principles such as Single Responsibility and Inversion of Control and patterns such as Dependency Injection and frameworks such as MoQ and Castle allow us to write code that is well suited to unit testing from the get go. Having unit tests in place for all known logic paths promotes code re-usability in that it allows common functions or responsibilities to be written separately and reused across areas of the application. When a change is made to the common function it can be unit tested by simply running the tests. If all tests pass, it ensures that the changes have not broken the logic flow in the unit and the other parts of the application will not suffer due to the change. The amount of manual regression testing for the impacted components is phenomenally reduced.

References

  • Quick start on MoQ: https://github.com/Moq/moq4/wiki/Quickstart
  • Handy tips on writing  unit tests using MoQ: http://www.developerhandbook.com/unit-testing/writing-unit-tests-with-nunit-and-moq/
  • The Art of Unit Testing in Ruby, Java & .NET - by Roy Osherove - http://artofunittesting.com/definition-of-a-unit-test/