Extending MSTest methods

I recently wrote a blog post about adding an Assert.Throws implementation for MSTest. This post goes into more detail of how I went about making MSTest extendible.

The basic premise of the first post was to add the Throws() method to MSTest. The implementation could be used as follows.
[TestMethod]
public void AddWithNegativeNumberThrowsExceptionExpectedMessage()
{
    // Arrange
    StringCalculator sc = new StringCalculator();
 
    // Act => Assert
    ExceptionAssert.Throws(() => sc.Add("-1"), "you cannot supply negative numbers.");
}
Regular MSTest users would appreciate the easier way of asserting an exception in a more readable manner, but they would also notice the need to type out ExceptionAssert.Throws() rather than Assert.Throws() as per the many other test methods of the framework e.g. Assert.AreEqual(), Assert.IsNull() and so on.

My initial plan was just to add the Throws() method as an extension method. Unfortunately the Microsoft.VisualStudio.TestTools.UnitTesting.Assert class is static, which put an end to that idea! This would mean I would have to create an extendible wrapper to sit on top of the MSTest Assert class. I want to retain all of the features of this class as well as being able to add my own extension methods.

The main element that acts as the central point for this to work is the following interface:
public interface IAssertion
{

}
We simply pass this around where we want to extend methods. The next step is to create the class we are going use for the instance of our Assert wrapper.
public class Assertion : IAssertion
{

}
I then moved over to the ExceptionAssert class and added an overloaded Throws() method that accepts an IAssertion parameter to make the method an extension method. This would read as:
public static void Throws<T>(this IAssertion assertion, Action task) where T : Exception
{
     Throws<T>(task, null, ExceptionMessageCompareOptions.None);
}
Now all we have to do, is add a variable in our class called Assert:
public static readonly IAssertion Assert = new Assertion();
In our tests we can use the same Assert. notation however it will be using the instance of Assertion. I took this further in the class library I created and included this in a base test class. This makes things very simple, since you can just download the package from Nuget, inhert from BaseTest in your current test class, and things just work. Here is an example:
[TestClass]
public class ThrowsTests : BaseTest
{
    [TestMethod]
    public void MethodThrowsException()
    {
        Assert.Throws(() => { throw new Exception(); });
    }
}

What about the default MSTest methods?


I created a wrapper class called MsTestAssertions which had the exact same method signatures from the real Assert class in MSTest. The difference is that we pass an IAssertion as the first parameter to make the method work as an extension. Each method calls the real implementation:
public static void AreEqual(this IAssertion assertion, System.Double expected, System.Double actual, System.Double delta, System.String message)
{
    Assert.AreEqual(expected, actual, delta, message);
}

Similar Post


After completing my first iteration of this code, I stumbled across this blog post from Daniel Cazzulino which proposes a very similar implementation. I took the idea of adding [DebuggerStepThrough] and [DebuggerNonUserCode] attributes to the classes of the library.

Get the Source Code


Github

You can download the full source here.

Download from Nuget


PM> Install-Package MSTestExtensions

See the Nuget project page.

5 comments:

  1. Have you attempted to encapsulate/extending nUnit's Constraint model, ex "That" and Is to your project?
    http://www.nunit.org/index.php?p=constraintModel&r=2.6

    ReplyDelete
  2. No I haven't, but it's a good idea. Thanks! I will look at adding this feature.

    ReplyDelete
  3. There are several existing libraries that do this and more, including one of my own called Specificity (on CodePlex an in Nuget).

    Specify.ThatAction(() => sc.Add("-1")).Should.Throw();

    A benefit to the approach in Specificity is that you have no base class. I worked hard to create a fluent API that worked very well with IntelliSense (some similar libraries "pollute" IntelliSense with lots of noise) and remained completely open to extension.

    ReplyDelete
    Replies
    1. Sounds good. The aim of this plug-in was to stay close to the default Assert methods provided by MSTest rather than add a complete Fluent API as per your example. Maybe post a link to your project for others to check out, if you like? :)

      Delete
    2. Well, I was not trying to market my library when I posted here, but since you bring it up some might be interested. http://specificity.codeplex.com.

      There are other libraries like this as well, and it's worth checking all of them out. My real point was that there are both pros and cons to the approach you took. For many, the need to use a base class makes this approach a non-starter. The fluent syntax isn't everyone's cup of tea, but there's non-fluent approaches that are similar to what I did with Specificity, such as the NUnit constraint model Cold Chilli mentioned. The syntax there is also something slightly less "normal" and some won't care for it either, but the benefit to both approaches is that you have a system that's open to extension (static asserts are not) while not requiring a base class.

      Delete