I have been using Rhino Mocks for past couple of years as the Dynamic mocking framework. It has support for different types of mocks like Strict mocks, Dynamic mocks and Partial mocks. The use of these types for specific use is bit confusing. Recently I had a wow moment when I found a real use of the Partial mock. This post is about that enlightenment.
Most of the time I have relied on using Strict mock and stubs. As mentioned in the documentation of the Rhino mocks, my understanding of the partial mock was that it is used to mock the abstract classes and methods. But that's not the only use of Partial mock. It can be used to also test methods by mocking only the methods we are interested in. Lets dive into an example.
How to use Partial Mocks
Lets assume we are building a solution for calculating the phone bill for a hypothetical phone company. As of now the company has two types of customers. The Normal ones and the Corporate customers. While calculating the total due amount for a customer, the phone company does not give any discount for the normal customers but corporate customers are eligible for the 25% discount on the billed amount. Here is an implementation
public class PhoneBillCalculator
{
private const int ROUNDED_DIGITS = 2;
private const double CORPORATE_DISCOUNT_PERCENTAGE = 0.25;
public PhoneBill GenerateBill(Customer customer)
{
PhoneBill generateBill = new PhoneBill
{
CustomerType = customer.CustomerType,
BilledAmount = customer.BilledAmount
};
if (customer.CustomerType == "Normal")
{
generateBill.DiscountedAmount = 0;
}
else
{
generateBill.DiscountedAmount = Math.Round(
customer.BilledAmount * CORPORATE_DISCOUNT_PERCENTAGE, ROUNDED_DIGITS, MidpointRounding.AwayFromZero);
}
double totalDueAmount = generateBill.BilledAmount - generateBill.DiscountedAmount;
generateBill.TotalDueAmount = Math.Round(totalDueAmount, ROUNDED_DIGITS, MidpointRounding.AwayFromZero);
return generateBill;
}
}
Lets build a small set of tests to test these conditions.
[TestClass]
public class PhoneBillCalculatorTest
{
[TestMethod]
public void GenerateBill_WithCustomerTypeAsNormal_ApplyNoDiscountOnTotalBilledAmount()
{
Customer customer = CreateCustomer("Normal", 170.50);
PhoneBillCalculator billCalculator = new PhoneBillCalculator();
PhoneBill phoneBill = billCalculator.GenerateBill(customer);
Assert.IsNotNull(phoneBill);
Assert.AreEqual("Normal", phoneBill.CustomerType);
Assert.AreEqual(170.50, phoneBill.BilledAmount);
Assert.AreEqual(0, phoneBill.DiscountedAmount);
Assert.AreEqual(170.50, phoneBill.TotalDueAmount);
}
[TestMethod]
public void GenerateBill_WithCustomerTypeAsCorporate_ApplyCorporateDiscountOnTotalBilledAmount()
{
Customer customer = CreateCustomer("Corporate", 170.50);
PhoneBillCalculator billCalculator = new PhoneBillCalculator();
PhoneBill phoneBill = billCalculator.GenerateBill(customer);
Assert.IsNotNull(phoneBill);
Assert.AreEqual("Corporate", phoneBill.CustomerType);
Assert.AreEqual(170.50, phoneBill.BilledAmount);
Assert.AreEqual(42.63, phoneBill.DiscountedAmount);
Assert.AreEqual(127.87, phoneBill.TotalDueAmount);
}
private static Customer CreateCustomer(string customerType, double billedAmount)
{
return new Customer
{
CustomerType = customerType,
BilledAmount = billedAmount
};
}
There are two tests. The first one which tests that there is no discount applied for the Normal customer’s total due amount. And the second test validates that the customer is given his due 25% discount on the original billed amount. Both the tests are pretty straightforward. Because this post is related to Partial mocks, lets create a scenario which forces us to make use of Rhino mocks.
Imagine there is an update to the original user requirement. The phone company has come up with more classifications for the customers. Based on various factors which are outside the scope of this post lets say the customers are classified as Normal, Corporate, Gold, Silver and Platinum. The discount varies for newly added types which are Gold, Silver and Platinum. Lets refactor the code to suite this requirement.
In its current state, the GenerateBill method only works for Normal and Corporate customers. We can refactor the if else block into a switch case statement. The first part which copies the values from customer to bill entity is common to all types of customers. It would be nice to refactor this into a separate method. Same is the case with the final part which computes the total due amount. Instead of going through step by step refactoring, I’ll directly show the final code snippet.
public PhoneBill GenerateBill(Customer customer)
{
PhoneBill generateBill = GetGenerateBillWithDefaultValues(customer);
CalculateDiscount(customer, generateBill);
CalculateTotalDueAmount(generateBill);
return generateBill;
}
After this refactoring, the GenerateBill method acts as template method which calls other methods. If we run the unit tests after this refactoring, they still run as expected. This ensures that we haven’t broken any of the existing functionality. There is one problem though. If we look at the tests they are testing the wrong thing. Observe carefully the data being setup in the test like the customer type and the billed amount. These values are not used in the GenerateBill method. We are setting data which is not in the scope of this method. If we follow TDD we should be testing only those things which are in the scope of the method under test. The existing unit tests are clearly violating this principle.
Lets see how to fix this. The template method is calling the methods within the same class. These are not external dependencies which can be mocked using dynamic mocks. Rhino Mocks has a special mock which can be used for cases like this. Its called Partial Mock which allows us to selectively mock parts of the class. Go ahead and add reference to the latest version of Rhino Mocks. I used NuGet package manager to add the dependency to the project. Lets see how we can make use of it in the code.
[TestMethod]
public void GenerateBill_WithCustomer_GeneratesBill()
{
Customer customer = CreateCustomer("Normal", 170.50);
PhoneBillCalculator billCalculator = CreatePhoneBillCalculator();
PhoneBill expectedBill = new PhoneBill();
billCalculator.Expect(x => x.GenerateBillWithDefaultValues(customer)).Return(expectedBill);
billCalculator.Expect(x => x.CalculateDiscount(customer, expectedBill));
billCalculator.Expect(x => x.CalculateTotalDueAmount(expectedBill));
PhoneBill phoneBill = billCalculator.GenerateBill(customer);
billCalculator.VerifyAllExpectations();
Assert.IsNotNull(phoneBill);
}
We have set expectations on the instance of a PhoneBillCalculator itself. These methods are Then we exercised the method under test which is GenerateBill and finally we verify that the phone bill which is returned is not null. We also verify that all the expectations set on the bill calculator are met successfully. All this magic is possible because of the partial mock which is created in the helper method
private PhoneBillCalculator CreatePhoneBillCalculator()
{
return MockRepository.GeneratePartialMock<PhoneBillCalculator>();
}
Note that we are not creating an instance of the PhoneBillCalculator class, instead we are generating a partial mock using the MockRepository. This gives us the flexibility of setting up expectations on the methods of PhoneBillCalculator class. Only prerequisite for using this approach is that the method we are going to set the expectation must be a public virtual method. Please refer to my other post to check how we can partially mock the internal methods using Rhino Mocks.
Lets modify the earlier two tests to use the helper method instead of creating a new instance inside the test method.
PhoneBillCalculator billCalculator = CreatePhoneBillCalculator();
We can run all the unit tests now and see that all 3 tests are run successfully. How is that possible? We modified the two tests to use the partial mock and did not set any expectations on the methods. Still the tests ran successfully. This is because if we don’t set any expectations on a partial mock, Rhino Mocks reverts to the actual implementation and executes the real code.
With the refactored code, the scope of the earlier tests changes accordingly. I’ll leave it to the readers as an exercise to refactor those tests and also add new ones for the other pieces of the code which resulted as part of the refactoring. And by the way we did not add any code related to the discount for Gold, Silver and Platinum type of customers. Well that’s an exercise for you. You can add the required code to the switch statement.
Conclusion
Sometimes we need to mock some methods of the class which is under test. In general while unit testing we create a concrete instance of the class under test. We cannot set expectations on the methods of a concrete instance of a class. Under these circumstances we can make of PartialMocks provided by RhinoMocks to mock only few methods of a class. Those methods which are not mocked will be executed as normal. This helps us in focusing on the methods under test and not to worry about the other methods which might be called internally. This can be helpful in building code which is more readable and maintainable. Small methods are always easier to maintain and test compared to the lengthy ones.
Note : I have tried to followed Roy Osherove’s advice to make the unit tests more readable and maintainable. Roy’s post in 2006 MSDN magazine is worth a read.
As always I have uploaded the complete working solution to Dropbox.
Until next time happy programming
Further Reading
For other topics related to unit testing, you can refer to the following book.
Thanks Deepak
ReplyDelete