Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / unit-testing

Using partial mocks for non public methods testing

5.00/5 (1 vote)
10 Feb 2013CPOL3 min read 45.9K   174  
Pattern to follow using mock frameworks so non public methods can be tested. The code example is based on the RhinoMock framework but it is easily applicable to any other mock framework.

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:

C#
/// <summary>
/// Determines the business <see cref="RuleType"/> from a given integer value
/// </summary>
public class BusinessRuleCalculator
{
    /// <summary>
    /// Determines the business <see cref="RuleType"/> from
    /// a passed value
    /// </summary>
    /// <param name="value">Integer value to determine the business value from</param>
    /// <returns>
    /// Zero if value is zero,
    /// see <see cref="GetRuleForNegative"/> for a negative value,
    /// see <see cref="GetRuleForPositive"/> for a positive value
    /// </returns>
    public  RuleType GetRule(int value)
    {
        if(value == 0) return RuleType.Zero;
        return value > 0
                   ? GetRuleForPositive(value)
                   : GetRuleForNegative(value);
    }

    /// <summary>
    /// Determines the business rule type for a negative value
    /// </summary>
    /// <param name="value">Negative value to determine the rule type</param>
    /// <returns>
    /// OverThreshold if the value is less than -10,
    /// NegativeAnomaly if the value is between -6 and -8 (inclusive),
    /// otherwise NegativeNormalCase
    /// </returns>
    protected virtual RuleType GetRuleForNegative(int value)
    {
        if(value < -10) return RuleType.OverThreshold;
        return value.IsBetween(-6, -8)
            ? RuleType.NegativeAnomaly
            : RuleType.NegativeNormalCase;
    }

    /// <summary>
    /// Determines the business rule type for a positive value
    /// </summary>
    /// <param name="value">Positive value to determine the rule type</param>
    /// <returns>
    /// OverThreshold if the value is greater than 10,
    /// PositiveAnomaly if the value is between 3 and 5 (inclusive),
    /// otherwise PositiveNormalCase
    /// </returns>
    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:

C#
[TestClass]
public class BusinessRuleCalculatorPublicApiTests : BusinessRuleCalculator
{
    /// <summary>
    /// If zero value is passed the method returns Zero
    /// </summary>
    [TestMethod]
    public void GetRuleForZeroCase()
    {
        var calculator = new BusinessRuleCalculator();
        const RuleType expected = RuleType.Zero;
        var result = calculator.GetRule(0);
        Assert.AreEqual(expected, result);
    }

    /// <summary>
    /// Value is positive so ensure that the GetRuleForPositive
    /// method is invoked and that the GetRuleForNegative is not
    /// </summary>
    [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();
    }

    /// <summary>
    /// Value is positive so ensure that the GetRuleForPositive
    /// method is invoked and that the GetRuleForNegative is not
    /// </summary>
    [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:

Image 1

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)