I have moved my active blog over to tumblr. I've maintained this blog for reference but will be posting to http://www.robustsoftware.co.uk instead. I've pointed my Feedburner feed to tumblr so if you're subscribed already you should already have switched with me.

More fluent assertions using extension methods

I'm a great believer in making your unit tests as easy to read and understand as possible. I was thinking about how I could improve my tests the other day and thought about using extension methods to create a more fluent way of creating complex assertions.

This has allowed me to create a complex assertion like:

  1. testObject.ShouldBeTheSameObjectAs(targetObject).And.ShouldBeEqualTo(testObject).And.ShouldSatisfy(x => x is Object);

Which I hope you'll agree reads a lot easier than those three assumptions on separate lines. Also, note that I have added the ability to use a predicate for when there isn't a method which quite matches your needs.

Here's the code for the extension methods:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using MbUnit.Framework;
  6.  
  7. namespace UnitTestingExtensions
  8. {
  9.     public static class FluentTestingExtensions
  10.     {
  11.         public static FluentAnd<T> ShouldSatisfy<T>(this T testTarget, Predicate<T> predicate)
  12.         {
  13.             Assert.IsTrue(predicate.Invoke(testTarget));
  14.             return new FluentAnd<T>(testTarget);
  15.         }
  16.  
  17.         public static FluentAnd<T> ShouldBeEqualTo<T>(this T testTarget, T comparisonObject)
  18.         {
  19.             Assert.AreEqual(testTarget, comparisonObject);
  20.             return new FluentAnd<T>(testTarget);
  21.         }
  22.  
  23.         public static FluentAnd<T> ShouldBeTheSameObjectAs<T>(this T testTarget, Object comparisonObject)
  24.         {
  25.             Assert.AreEqual(testTarget, comparisonObject);
  26.             return new FluentAnd<T>(testTarget);
  27.         }
  28.  
  29.         public class FluentAnd<T>
  30.         {
  31.             public T And { get; private set; }
  32.  
  33.             public FluentAnd(T target)
  34.             {
  35.                 this.And = target;
  36.             }
  37.         }
  38.     }
  39.  
  40. }

And here are the tests for the extension methods:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using MbUnit.Framework;
  6.  
  7. namespace UnitTestingExtensions.FluentTestingExtensionsTests
  8. {
  9.     [TestFixture]
  10.     public class ShouldSatisfyTests
  11.     {
  12.         [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
  13.         public void PredicateEvaluatesAsFalseAndTestFails()
  14.         {
  15.             int testNumber = 4;
  16.  
  17.             testNumber.ShouldSatisfy(x => x > 10);
  18.         }
  19.  
  20.         [Test]
  21.         public void PredicateEvaluatesAsTrueAndTestPasses()
  22.         {
  23.             int testNumber = 4;
  24.  
  25.             testNumber.ShouldSatisfy(x => x == 4);
  26.         }
  27.     }
  28.  
  29.     [TestFixture]
  30.     public class ShouldBeEqualToTests
  31.     {
  32.         [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
  33.         public void EqualsCheckFailsAndTestFails()
  34.         {
  35.             int testNumber = 4;
  36.  
  37.             testNumber.ShouldBeEqualTo(7);
  38.         }
  39.  
  40.         [Test]
  41.         public void EqualsCheckPassesAndTestPasses()
  42.         {
  43.             int testNumber = 4;
  44.  
  45.             testNumber.ShouldBeEqualTo(4);
  46.         }
  47.     }
  48.  
  49.     [TestFixture]
  50.     public class ShouldBeTheSameObjectAsTests
  51.     {
  52.         [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
  53.         public void DifferentObjectsSoTestFails()
  54.         {
  55.             Object testObject = new Object();
  56.  
  57.             testObject.ShouldBeTheSameObjectAs(new Object());
  58.         }
  59.  
  60.         [Test]
  61.         public void TestPassesWhenTheObjectsAreTheSame()
  62.         {
  63.             Object testObject = new Object();
  64.             Object checkObject = testObject;
  65.  
  66.             testObject.ShouldBeTheSameObjectAs(checkObject);
  67.         }
  68.     }
  69.  
  70.     [TestFixture]
  71.     public class AndTests
  72.     {
  73.         [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
  74.         public void TestFailsIfFirstCaseFails()
  75.         {
  76.             int testNumber = 4;
  77.  
  78.             testNumber.ShouldBeEqualTo(3).And.ShouldSatisfy(x => x > 1);
  79.         }
  80.  
  81.         [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
  82.         public void TestFailsIfSecondCaseFails()
  83.         {
  84.             int testNumber = 4;
  85.  
  86.             testNumber.ShouldBeEqualTo(4).And.ShouldSatisfy(x => x > 10);
  87.         }
  88.  
  89.         [Test]
  90.         public void TestPassesIfBothCasesPass()
  91.         {
  92.             int testNumber = 4;
  93.  
  94.             testNumber.ShouldSatisfy(x => x > 1).And.ShouldBeEqualTo(4);
  95.         }
  96.  
  97.         [Test]
  98.         public void TestPassesWhenThreeCasesAreUsed()
  99.         {
  100.             Object testObject = new Object();
  101.             Object targetObject = testObject;
  102.  
  103.             testObject.ShouldBeTheSameObjectAs(targetObject).And.ShouldBeEqualTo(testObject).And.ShouldSatisfy(x => x is Object);
  104.         }
  105.     }
  106. }

I'd love to hear some feedback on what you think of this style.