More fluent assertions using extension methods
Posted by Garry Shutler in Extension methods, MbUnit, Unit testing
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:
- 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:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using MbUnit.Framework;
- namespace UnitTestingExtensions
- {
- public static class FluentTestingExtensions
- {
- public static FluentAnd<T> ShouldSatisfy<T>(this T testTarget, Predicate<T> predicate)
- {
- Assert.IsTrue(predicate.Invoke(testTarget));
- return new FluentAnd<T>(testTarget);
- }
- public static FluentAnd<T> ShouldBeEqualTo<T>(this T testTarget, T comparisonObject)
- {
- Assert.AreEqual(testTarget, comparisonObject);
- return new FluentAnd<T>(testTarget);
- }
- public static FluentAnd<T> ShouldBeTheSameObjectAs<T>(this T testTarget, Object comparisonObject)
- {
- Assert.AreEqual(testTarget, comparisonObject);
- return new FluentAnd<T>(testTarget);
- }
- public class FluentAnd<T>
- {
- public T And { get; private set; }
- public FluentAnd(T target)
- {
- this.And = target;
- }
- }
- }
- }
And here are the tests for the extension methods:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using MbUnit.Framework;
- namespace UnitTestingExtensions.FluentTestingExtensionsTests
- {
- [TestFixture]
- public class ShouldSatisfyTests
- {
- [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
- public void PredicateEvaluatesAsFalseAndTestFails()
- {
- int testNumber = 4;
- testNumber.ShouldSatisfy(x => x > 10);
- }
- [Test]
- public void PredicateEvaluatesAsTrueAndTestPasses()
- {
- int testNumber = 4;
- testNumber.ShouldSatisfy(x => x == 4);
- }
- }
- [TestFixture]
- public class ShouldBeEqualToTests
- {
- [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
- public void EqualsCheckFailsAndTestFails()
- {
- int testNumber = 4;
- testNumber.ShouldBeEqualTo(7);
- }
- [Test]
- public void EqualsCheckPassesAndTestPasses()
- {
- int testNumber = 4;
- testNumber.ShouldBeEqualTo(4);
- }
- }
- [TestFixture]
- public class ShouldBeTheSameObjectAsTests
- {
- [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
- public void DifferentObjectsSoTestFails()
- {
- Object testObject = new Object();
- testObject.ShouldBeTheSameObjectAs(new Object());
- }
- [Test]
- public void TestPassesWhenTheObjectsAreTheSame()
- {
- Object testObject = new Object();
- Object checkObject = testObject;
- testObject.ShouldBeTheSameObjectAs(checkObject);
- }
- }
- [TestFixture]
- public class AndTests
- {
- [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
- public void TestFailsIfFirstCaseFails()
- {
- int testNumber = 4;
- testNumber.ShouldBeEqualTo(3).And.ShouldSatisfy(x => x > 1);
- }
- [Test, ExpectedException(typeof(MbUnit.Core.Exceptions.AssertionException))]
- public void TestFailsIfSecondCaseFails()
- {
- int testNumber = 4;
- testNumber.ShouldBeEqualTo(4).And.ShouldSatisfy(x => x > 10);
- }
- [Test]
- public void TestPassesIfBothCasesPass()
- {
- int testNumber = 4;
- testNumber.ShouldSatisfy(x => x > 1).And.ShouldBeEqualTo(4);
- }
- [Test]
- public void TestPassesWhenThreeCasesAreUsed()
- {
- Object testObject = new Object();
- Object targetObject = testObject;
- testObject.ShouldBeTheSameObjectAs(targetObject).And.ShouldBeEqualTo(testObject).And.ShouldSatisfy(x => x is Object);
- }
- }
- }
I'd love to hear some feedback on what you think of this style.
My version of a SmartBag
Posted by Garry Shutler in ASP.NET MVC, SmartBag
After seeing Jeffrey Palermo's SmartBag (part 1 and part 2) I was inspired to create my own version which was more specific to my needs. For one I'm a lover of the IEnumerable<T> interface, especially when combined with the power of LINQ for Objects.
Anyway, enough of my waffle, here's the code for the class.
- using System;
- using System.Collections.Generic;
- using System.Collections;
- using System.Text;
- namespace SmartBag
- {
- public class SmartBag
- {
- private Hashtable _hashtable;
- public SmartBag()
- {
- this._hashtable = new Hashtable();
- }
- public void Add<T>(T item)
- {
- List<T> list;
- if (!this._hashtable.ContainsKey(typeof(T)))
- this._hashtable.Add(typeof(T), new List<T>());
- list = this._hashtable[typeof(T)] as List<T>;
- list.Add(item);
- }
- public IEnumerable<T> Get<T>()
- {
- if (!this._hashtable.ContainsKey(typeof(T)))
- throw new IndexOutOfRangeException(string.Format("There are no entries for {0}", typeof(T).Name));
- foreach (T item in this._hashtable[typeof(T)] as List<T>)
- yield return item;
- }
- }
- }
There are basically two methods to it, Add and Get. Add adds items of the specified type to a list of that type. Get retrieves the list of the specified type.
Below are my test cases which should give you some ideas of how you can use it.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using MbUnit.Framework;
- namespace SmartBag
- {
- [TestFixture]
- class SmartBagTest
- {
- [Test]
- public void AClassAddedAgainstAnInterfaceCanBeRetrievedTogether()
- {
- SmartBag bag = new SmartBag();
- bag.Add<IContract>(new Parent());
- bag.Add<IContract>(new Child());
- Assert.AreEqual(2, bag.Get<IContract>().Count());
- }
- [Test]
- public void AChildWillBeAddedToItsParentsEnumerationIfYouDefineIt()
- {
- SmartBag bag = new SmartBag();
- bag.Add(new Parent());
- bag.Add<Parent>(new Child());
- Assert.AreEqual(2, bag.Get<Parent>().Count());
- }
- [Test, ExpectedException(typeof(IndexOutOfRangeException))]
- public void AnExpectionWillBeRaiseWhenAttemptingToRetrieveATypeThatDoesNotExist()
- {
- SmartBag bag = new SmartBag();
- foreach (Object item in bag.Get<Object>()) { } // must enumerate for it to be evaluated
- }
- [Test]
- public void ShouldBeAbleToAddChildAsADifferentType()
- {
- SmartBag bag = new SmartBag();
- bag.Add(new Parent());
- bag.Add(new Child());
- Assert.AreEqual(1, bag.Get<Parent>().Count());
- Assert.AreEqual(1, bag.Get<Child>().Count());
- }
- private class Parent : IContract
- {
- }
- private class Child : Parent
- {
- }
- private interface IContract
- {
- }
- }
- }
To begin with this is a basic idea. I hope to utilise it in an upcoming project and it will probably look a bit different after I've used it in anger. Something I can see I might need is to extract an interface to make testing easier but I've left that out for now as YAGNI may apply.
Any feedback would be greatly appreciated.