Introduction
If you use any mock frameworks in your unit tests and you are interested in
finding an approach for testing the non public functionality within your
classes, this article discusses one possible approach for the problem. The
described solution and the article's code example uses the RhinoMock framework
but it should be easily applicable to any other mock framework.
Background
Many people would argue the need for testing non public methods in classes, and I may agree at some extend but there are times when I want to go beyond the public class API and produce tests that can mock internal behaviour, some of the drivers for doing so are:
- More concise and 'smaller' tests
- Easier to maintain and understand
- Unit test to declare/describe contracts between the public and the non public methods within a class
helping others to understand class design
Many may argue that building tests on private methods will lead to brittle tests that break easily when you re-factor private implementations, which is another point to have in mind; but again there are times that you just find that testing a private method in isolation makes a lot more sense and it should make other's life easier when later they need to maintain your code. I see tests as the best tool to articulate the business requirements, scope and developer's concerns.
Some may argue that in order to resolve above concerns, maybe the best approach is to re-locate that functionality in external classes and expose that functionality in public methods. Which again is a very good point that I tend to agree most times; but there is a limit how far I want to break the code into classes. Some other people may argue to change the non public methods into public ones so they can be tested in the way we desire; but I found this approach is wrong: it pollutes the class 'API' in a way it was not intended.
A solution to the problem is available in some of the mock frameworks, for example Moq provides the Protected
helper method and JustMock has the NonPublic
method. A disadvantage with both approaches is how fragile the mechanism is if the method signature is re-factored. But it may do the trick for some people
The article proposes a simple solution applicable to most mock frameworks:
- Make the private methods protected and virtual
- The text class inherits from the class to test
- Use partial mocks to get the desire hybrid behavior
The Class to Test
In order to demonstrate the approach, I put together a simple class that exposes a single method that delegates to two private methods; the example is over-simplified but it should be sufficient to demonstrate how you can apply it to your own tests. The example class takes an integer an returns an enumeration value depending on a set of simple business requirements:
public class BusinessRuleCalculator
{
public RuleType GetRule(int value)
{
if(value == 0) return RuleType.Zero;
return value > 0
? GetRuleForPositive(value)
: GetRuleForNegative(value);
}
protected virtual RuleType GetRuleForNegative(int value)
{
if(value < -10) return RuleType.OverThreshold;
return value.IsBetween(-6, -8)
? RuleType.NegativeAnomaly
: RuleType.NegativeNormalCase;
}
protected virtual RuleType GetRuleForPositive(int value)
{
if(value > 10) return RuleType.OverThreshold;
return value.IsBetween(3, 5)
? RuleType.PositiveAnomaly
: RuleType.PositiveNormalCase;
}
}
So instead of putting tests invoking the GetRule
for all possible business cases, we are going to test the following:
- If a zero value is passed then the method returns Zero enumeration instance
- If a positive value is passed the method invokes the
GetRuleForPositive
method but not the GetRuleForNegative
- If a negative value is passed the method invokes the
GetRuleForNegative
method but not the GetRuleForPositive
Then, we should delegate to other set of tests to validate the private (protected) methods are working correctly.
Test Class
So as motioned above, we created the BusinessRuleCalculatorPublicApiTests
class:
[TestClass]
public class BusinessRuleCalculatorPublicApiTests : BusinessRuleCalculator
{
[TestMethod]
public void GetRuleForZeroCase()
{
var calculator = new BusinessRuleCalculator();
const RuleType expected = RuleType.Zero;
var result = calculator.GetRule(0);
Assert.AreEqual(expected, result);
}
[TestMethod]
public void GetRuleForPositiveValue()
{
var calculator = MockRepository.GeneratePartialMock<BusinessRuleCalculatorPublicApiTests>();
const int value = 1;
const RuleType expected = RuleType.PositiveNormalCase;
calculator.Expect(c => c.GetRuleForPositive(value))
.Return(expected)
.Repeat.Once();
calculator.Expect(c => c.GetRuleForNegative(value))
.IgnoreArguments()
.Repeat.Never();
var result = calculator.GetRule(value);
Assert.AreEqual(expected, result);
calculator.VerifyAllExpectations();
}
[TestMethod]
public void GetRuleForNegativeValue()
{
var calculator = MockRepository.GeneratePartialMock<BusinessRuleCalculatorPublicApiTests>();
const int value = -1;
const RuleType expected = RuleType.NegativeNormalCase;
calculator.Expect(c => c.GetRuleForPositive(value))
.IgnoreArguments()
.Repeat.Never();
calculator.Expect(c => c.GetRuleForNegative(value))
.Return(expected)
.Repeat.Once();
var result = calculator.GetRule(value);
Assert.AreEqual(expected, result);
calculator.VerifyAllExpectations();
}
}
As the BusinessRuleCalculatorPublicApiTests
class inherits from the class to test, we can create a partial mock and then
replace the original implementation of the GetRuleForNegative
and GetRuleForPositive
so when the public method
is invoked our expectations are executed instead. Please note, that making the protected method virtual
ensures the partial mock works correctly, otherwise the original protected method implementation is still invoked:
Summary
You may download the example code provided within this article and try for yourself. Hopefully you may find it helpful next time you are creating unit tests.