Monday, February 1, 2010

Assert Helper Class

Today I'll share one of my simple but handy helper classes. Unit testing frameworks like Visual Studio Unit Tests or NUnit usually provide a validation class usually called "Assert" that provides a large set of static methods for test result validation. Without those frameworks the standard .NET way for runtime validation in debug sessions is System.Diagnostics.Debug.Asser(bool) method. Generally, this method works fine, but I prefer the more handy methods of unit testing frameworks like AreEqual(arg1, arg2) or IsNotNull(arg). Therefore, I created a simple static helper class called Assert which covers most of those test framework methods.

The class does nothing special, it just maps the standard Debug.Assert and Debug.Fail methods to a more sophisticated interface. Like .NET Debug.Assert method, all methods of the class are marked with System.Diagnostics.ConditionalAttribute and condition "DEBUG". So all method calls in project code become removed when compiling in "Release Mode".

Here's a little sample which shows how to use the class compared with Debug.Assert method.
// input parameter validation in private methods
Assert.NullArg(someParam, "someParam");
Debug.Assert(someParam != null, "someParam cannot be null");

// not null validation
List<int> list = new List<int>();
Assert.IsNotNull(list);
Debug.Assert(list != null);

// null validation
object nullValue = null;
Assert.IsNull(nullValue);
Debug.Assert(nullValue == null);

// equality validation of reference types (which usually
// don't overwrite the "==" operator)
string s1 = "foo", s2 = "foo";
Assert.AreEqual(s1, s2);
Debug.Assert(
   (s1 == null && s2 == null)
   || (s1 != null && s1.Equals(s2))
   );

// type validation
object o1 = "foo";
Assert.IsOfType(o1, typeof(string));
Debug.Assert(
   o1 != null 
   && typeof(string).IsAssignableFrom(o1.GetType()));

// not equal validation with error text
int num1 = 1, num2 = 2;
Assert.AreNotEqual(num1, num2, "{0} cannot be {1}", num1, num2);
Debug.Assert(num1 != num2,
             string.Format("{0} cannot be {1}", num1, num2));


And here's the complete code of the class.
using System;
using System.Diagnostics;
using System.Globalization;

namespace ClassLibrary1.Diagnostics {
   /// <summary>
   /// Helper class for debug assertions.
   /// </summary>
   /// <remarks>
   /// This class represents a wrapper of 
   /// <see cref="System.Diagnostics.Debug"/> class which provides a 
   /// richer interface and enables a simplified debug assertion.
   /// <p/>
   /// All methods are marked with 
   /// <see cref="System.Diagnostics.ConditionalAttribute"/> with 
   /// "DEBUG" condition string. This causes the method calls to be 
   /// removed from production source code when compiling in "Release" 
   /// mode.
   /// </remarks>
   public static class Assert {
      /// <summary>
      /// Asserts two specified argiments to be equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for 
      /// equality.</param>
      /// <param name="arg2">The first argument to be compared for 
      /// equality.</param>
      [Conditional("DEBUG")]
      public static void AreEqual(object arg1, object arg2) {
         if (arg1 == null && arg2 == null)
            return;
         if (arg1 == null)
            Debug.Assert(false);
         Debug.Assert(arg1.Equals(arg2));
      }

      /// <summary>
      /// Asserts two specified argiments to be equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for 
      /// equality.</param>
      /// <param name="arg2">The first argument to be compared for 
      /// equality.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void AreEqual(object arg1, object arg2, string message, 
                                  params object[] args) {
         if (arg1 == null && arg2 == null)
            return;
         if (arg1 == null)
            Debug.Assert(false, GetMessage(message, args));
         Debug.Assert(arg1.Equals(arg2), GetMessage(message, args));
      }

      /// <summary>
      /// Asserts two specified argiments to be equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for 
      /// equality.</param>
      /// <param name="arg2">The first argument to be compared for 
      /// equality.</param>
      [Conditional("DEBUG")]
      public static void AreEqual<T>(T arg1, T arg2) {
         if (arg1 == null && arg2 == null)
            return;
         if (arg1 == null)
            Debug.Assert(false);
         Debug.Assert(arg1.Equals(arg2));
      }

      /// <summary>
      /// Asserts two specified argiments to be equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for 
      /// equality.</param>
      /// <param name="arg2">The first argument to be compared for 
      /// equality.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void AreEqual<T>(T arg1, T arg2, string message, 
                                     params object[] args) {
         if (arg1 == null && arg2 == null)
            return;
         if (arg1 == null)
            Debug.Assert(false, GetMessage(message, args));
         Debug.Assert(arg1.Equals(arg2), GetMessage(message, args));
      }

      /// <summary>
      /// Asserts two specified argiments to be not equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="arg2">The first argument to be compared for not 
      /// being equal.</param>
      [Conditional("DEBUG")]
      public static void AreNotEqual(object arg1, object arg2) {
         if (arg1 == null && arg2 == null)
            Debug.Assert(false);
         if (arg1 == null)
            return;
         Debug.Assert(!arg1.Equals(arg2));
      }

      /// <summary>
      /// Asserts two specified argiments to be not equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="arg2">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void AreNotEqual(object arg1, object arg2, string message, 
                                     params object[] args) {
         if (arg1 == null && arg2 == null)
            Debug.Assert(false, GetMessage(message, args));
         if (arg1 == null)
            return;
         Debug.Assert(!arg1.Equals(arg2), GetMessage(message, args));
      }

      /// <summary>
      /// Asserts two specified argiments to be not equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="arg2">The first argument to be compared for not 
      /// being equal.</param>
      [Conditional("DEBUG")]
      public static void AreNotEqual<T>(T arg1, T arg2) {
         if (arg1 == null && arg2 == null)
            Debug.Assert(false);
         if (arg1 == null)
            return;
         Debug.Assert(!arg1.Equals(arg2));
      }

      /// <summary>
      /// Asserts two specified argiments to be not equal to each other.
      /// </summary>
      /// <param name="arg1">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="arg2">The first argument to be compared for not 
      /// being equal.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void AreNotEqual<T>(T arg1, T arg2, string message, 
                                        params object[] args) {
         if (arg1 == null && arg2 == null)
            Debug.Assert(false, GetMessage(message, args));
         if (arg1 == null)
            return;
         Debug.Assert(!arg1.Equals(arg2), GetMessage(message, args));
      }

      /// <summary>
      /// Generates a failing assertion.
      /// </summary>
      [Conditional("DEBUG")]
      public static void Fail() {
         Debug.Assert(false);
      }

      /// <summary>
      /// Generates a failing assertion.
      /// </summary>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void Fail(string message, params object[] args) {
         Debug.Assert(false, GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified value not to be null.
      /// </summary>
      /// <param name="value">The value to be asserted not to be null.
      /// </param>
      [Conditional("DEBUG")]
      public static void IsNull(object value) {
         Debug.Assert(value == null);
      }

      /// <summary>
      /// Asserts a specified value not to be null.
      /// </summary>
      /// <param name="value">The value to be asserted not to be null.
      /// </param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      public static void IsNull(object value, string message, 
                                params object[] args) {
         Debug.Assert(value == null, GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified value to be null.
      /// </summary>
      /// <param name="value">The value to be asserted to be null.
      /// </param>
      [Conditional("DEBUG")]
      public static void IsNotNull(object value) {
         Debug.Assert(value != null);
      }

      /// <summary>
      /// Asserts a specified value to be null.
      /// </summary>
      /// <param name="value">The value to be asserted to be null.
      /// </param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for 
      /// specified message.</param>
      [Conditional("DEBUG")]
      public static void IsNotNull(object value, string message, 
                                   params object[] args) {
         Debug.Assert(value != null, GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified parameter to be null.
      /// </summary>
      /// <param name="value">The parameter to be asserted to be null.
      /// </param>
      /// <param name="paramName">The name of the parameter not to be 
      /// null.</param>
      /// <remarks>
      /// The difference between <see cref="NullArg"/> and IsNotNull
      /// is that <see cref="NullArg"/> should be used for parameters 
      /// provided to a method. IsNotNull should be used for other use 
      /// cases like method return values or conditional variable 
      /// initializations.
      /// </remarks>
      [Conditional("DEBUG")]
      public static void NullArg(object value, string paramName) {
         if (value == null)
            Debug.Fail(string.Concat(paramName + " cannot be null"));
      }

      /// <summary>
      /// Asserts a specified value to be assignable by a specified 
      /// type.
      /// </summary>
      /// <param name="value">The value to be asserted.</param>
      /// <param name="expectedType">The type to validate the specified 
      /// value to be assignable from.</param>
      [Conditional("DEBUG")]
      public static void IsOfType(object value, Type expectedType) {
         if (value == null)
            Debug.Assert(false);
         Debug.Assert(expectedType.IsAssignableFrom(value.GetType()));
      }

      /// <summary>
      /// Asserts a specified value to be assignable by a specified type.
      /// </summary>
      /// <param name="value">The value to be asserted.</param>
      /// <param name="expectedType">The type to validate the specified 
      /// value to be assignable from.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void IsOfType(object value, Type expectedType, 
                                  string message, params object[] args) {
         if (value == null)
            Debug.Assert(false, GetMessage(message, args));
         Debug.Assert(expectedType.IsAssignableFrom(value.GetType()), 
                      GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified value not to be assignable by a specified 
      /// type.
      /// </summary>
      /// <param name="value">The value to be asserted.</param>
      /// <param name="expectedType">The type to validate the specified 
      /// value not to be assignable from.</param>
      [Conditional("DEBUG")]
      public static void IsNotOfType(object value, Type expectedType) {
         if (value == null)
            Debug.Assert(false);
         Debug.Assert(!expectedType.IsAssignableFrom(value.GetType()));
      }

      /// <summary>
      /// Asserts a specified value not to be assignable by a specified 
      /// type.
      /// </summary>
      /// <param name="value">The value to be asserted.</param>
      /// <param name="expectedType">The type to validate the specified 
      /// value not to be assignable from.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void IsNotOfType(object value, Type expectedType, 
                                     string message, params object[] args) {
         if (value == null)
            Debug.Assert(false, GetMessage(message, args));
         Debug.Assert(!expectedType.IsAssignableFrom(value.GetType()), 
                      GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a target type to be assignable by a expected type.
      /// </summary>
      /// <param name="targetType">The target type to be assignable.
      /// </param>
      /// <param name="expectedType">The expected type to be assignable 
      /// by specified target type.</param>
      [Conditional("DEBUG")]
      public static void IsTypeOf(Type targetType, Type expectedType) {
         Debug.Assert(expectedType.IsAssignableFrom(targetType));
      }

      /// <summary>
      /// Asserts a target type to be assignable by a expected type.
      /// </summary>
      /// <param name="targetType">The target type to be assignable.
      /// </param>
      /// <param name="expectedType">The expected type to be assignable 
      /// by specified target type.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void IsTypeOf(Type targetType, Type expectedType, 
                                  string message, params object[] args) {
         Debug.Assert(expectedType.IsAssignableFrom(targetType), 
                      GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified condition to be true.
      /// </summary>
      /// <param name="condition">The condition to be asserted for being 
      /// true.</param>
      [Conditional("DEBUG")]
      public static void IsTrue(bool condition) {
         Debug.Assert(condition);
      }

      /// <summary>
      /// Asserts a specified condition to be true.
      /// </summary>
      /// <param name="condition">The condition to be asserted for being 
      /// true.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void IsTrue(bool condition, string message, 
                                params object[] args) {
         Debug.Assert(condition, GetMessage(message, args));
      }

      /// <summary>
      /// Asserts a specified condition not to be true.
      /// </summary>
      /// <param name="condition">The condition to be asserted for not 
      /// being true.</param>
      [Conditional("DEBUG")]
      public static void IsFalse(bool condition) {
         Debug.Assert(!condition);
      }

      /// <summary>
      /// Asserts a specified condition not to be true.
      /// </summary>
      /// <param name="condition">The condition to be asserted for not 
      /// being true.</param>
      /// <param name="message">The message to be shown when assertion 
      /// fails.</param>
      /// <param name="args">Optional formatting arguments for specified 
      /// message.</param>
      [Conditional("DEBUG")]
      public static void IsFalse(bool condition, string message, 
                                 params object[] args) {
         Debug.Assert(!condition, GetMessage(message, args));
      }

      private static string GetMessage(string message, 
                                       params object[] args) {
         if (args != null && args.Length != 0)
            return string.Format(CultureInfo.InvariantCulture, message, args);
         return message;
      }
   }
}