Friday, March 25, 2011

Explicitly implement interface to assist with Unit Testing and Method Accessibility

I commonly encounter the following scenario when I am designing a class and unit tests:

  • I want to break down a public method into smaller methods so that I can unit test complex logic in more manageable chunks
  • I want to limit the accessibility of my smaller methods to private

A solution to this problem is to have my class explicitly implement an internal interface.  The internal interface contains signatures for all of the private smaller methods.

These code samples demonstrate my solution.  The SourceCode class implements the ISourceCode interface for consumption by other assemblies and also explicitly implements the ISourceCodeUnitTest interface for unit testing purposes.  Unit tests are able to isolate the logic in the ValidateString method.  Other unit tests isolate the logic in the MyPublicValidator method without having to re-test the ValidateString method because ValidateString can be stubbed.

Source Code:

  1. using System;
  2.  
  3. namespace Blog
  4. {
  5.     public interface ISourceCode
  6.     {
  7.         string MyPublicValidator(string input);
  8.     }
  9.  
  10.     internal interface ISourceCodeUnitTest
  11.     {
  12.         bool ValidateString(string input);
  13.         string MyPublicValidator(ISourceCodeUnitTest sourceCode, string input);
  14.     }
  15.  
  16.     public class SourceCode : ISourceCode, ISourceCodeUnitTest
  17.     {
  18.         public string MyPublicValidator(string input)
  19.         {
  20.             return ((ISourceCodeUnitTest)this).MyPublicValidator(this, input);
  21.         }
  22.  
  23.         string ISourceCodeUnitTest.MyPublicValidator(ISourceCodeUnitTest sourceCode, string input)
  24.         {
  25.             // complex logic in separate ValidateString method
  26.             bool isValid = sourceCode.ValidateString(input);
  27.  
  28.             if (isValid)
  29.                 return "The input is a valid string.";
  30.             else return "The input is not valid.";
  31.         }
  32.  
  33.         bool ISourceCodeUnitTest.ValidateString(string input)
  34.         {
  35.             // complex logic
  36.             return string.Compare(input, "valid string", true) != -1;
  37.         }
  38.     }
  39. }
Unit Tests:

  1. using System;
  2. using NUnit.Framework;
  3. using Rhino.Mocks;
  4.  
  5. namespace Blog.Tests.Unit
  6. {
  7.     public class SourceCodeTests
  8.     {
  9.         [Test]
  10.         public void MyPublicValidator_InvalidInput_ReturnsInvalidMessage()
  11.         {
  12.             var stubSourceCode = MockRepository.GenerateStub<ISourceCodeUnitTest>();
  13.             stubSourceCode.Stub(sc => sc.ValidateString("input string")).Return(false);
  14.  
  15.             ISourceCodeUnitTest obj = new SourceCode();
  16.             Assert.That(obj.MyPublicValidator(stubSourceCode, "input string"), Is.EqualTo("The input is not valid."));
  17.         }
  18.         [Test]
  19.         public void MyPublicValidator_ValidInput_ReturnsValidMessage()
  20.         {
  21.             var stubSourceCode = MockRepository.GenerateStub<ISourceCodeUnitTest>();
  22.             stubSourceCode.Stub(sc => sc.ValidateString("input string")).Return(true);
  23.  
  24.             ISourceCodeUnitTest obj = new SourceCode();
  25.             Assert.That(obj.MyPublicValidator(stubSourceCode, "input string"), Is.EqualTo("The input is a valid string."));
  26.         }
  27.         [Test]
  28.         public static void ValidateString_ValidString_ReturnsTrue()
  29.         {
  30.             ISourceCodeUnitTest obj = new SourceCode();
  31.             Assert.That(obj.ValidateString("valid string"), Is.True);
  32.         }
  33.         [Test]
  34.         public static void ValidateString_InvalidString_ReturnsFalse()
  35.         {
  36.             ISourceCodeUnitTest obj = new SourceCode();
  37.             Assert.That(obj.ValidateString("invalid"), Is.False);
  38.         }            
  39.     }
  40. }