06:26 pm
March 03
Using partial mocks for non public methods testing
( 0 Votes )
Written by: Blogging Team Views : 2488
Attachments with this blog:
Source code 

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.

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, here you can find 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.

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 behavior, 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 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.

Here I propose 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.

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 and integer an returns an enumeration value depending on a set of simple business requirements:

namespace MockByExample.Calculators
{
/// <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 GerRuleForNegative

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.

So as motioned above, we created the BusinessRuleCalculatorPublicApiTests class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MockByExample.Calculators;
using Rhino.Mocks;

namespace MockByExample.Tests
{
    [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:

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.



( 0 Votes )