Today a little article about testing. You've may experienced situations when you wanted to thest a piece of code and the goal was to make sure, that a certain exception wasn't thrown.
I had the same situation some time ago and I searched for something like Assert.MustNotThrow<...>(...), but there was nothing like that. In my case, I'd used the well known Component Test Project for .NET-Framework (also known as MSTest project for .NET-Framework).
For this blog entry, I shortly checked the some other test frameworks for C# and found out, that only NUnit provides an Assert.DoesNotThrow(...). Unfortunately you cannot specify which exception shall not be thrown. So this may make test useless - an explanation to that you can find later in this post.
However, so I decided to create an extension for my need. The code is written for MSTest project for .NET-Framework but with some small changes you can use it in other test frameworks as well.
Here's the code, I will explain some specifics afterwards:
using System;
using System.Diagnostics;
using System.Linq;
// leave the namespace - this is
required in order to suppress unwanted stack trace information when
namespace
Microsoft.VisualStudio.TestTools.UnitTesting
{
/// <summary>
/// Contains assertion approaches for unit
testing which are not covered by standard framework
/// </summary>
public static class AssertExtension
{
/// <summary>
/// Tests whether the code specified by
delegate action does not throws given exception
/// of type <c>T</c>
(excluding derived types) and throws <c>AssertFailedException</c>
/// if code throws exception of type <c>T</c>.
/// </summary>
/// <typeparam name="T">
/// Type of exception expected not to be
thrown.
/// </typeparam>
/// <param name="assert">
/// The pseudo singleton instance of <c>Assert</c>
/// </param>
/// <param name="action">
/// Delegate to code to be tested and which
is expected not to throw specified exception.
/// </param>
/// <exception cref="Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
/// Thrown if action does throws exception of
type <c>T</c>.
/// </exception>
[DebuggerHidden]
public static void MustNotThrow<T>(this Assert assert, Action action) where T : Exception
=> MustNotThrow(assert,
action, typeof(T));
/// <summary>
/// Tests whether the code specified by
delegate action does not throws given exceptions
/// defined by <c>exceptionTypes</c> and throws <c>AssertFailedException</c>
/// if code throws exception of type given by
<c>exceptionTypes</c>.
/// </summary>
/// <typeparam name="exceptionTypes">
/// Types of exceptions expected not to be
thrown.
/// </typeparam>
/// <param name="assert">
/// The pseudo singleton instance of <c>Assert</c>
/// </param>
/// <param name="action">
/// Delegate to code to be tested and which
is expected not to throw specified exception.
/// </param>
/// <exception cref="Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
/// Thrown if
action does throws exception of type specified with <c>exceptionTypes</c>.
/// </exception>
[DebuggerHidden]
public static void MustNotThrow(this Assert assert, Action action, params Type[] exceptionTypes)
{
try
{
action();
}
catch (Exception ex)
{
if (!(exceptionTypes
?? throw new
ArgumentNullException(nameof(exceptionTypes))).Any()
||
exceptionTypes.Any(x => x == ex.GetType()))
{
throw new
AssertFailedException($"{nameof(Assert)}.{nameof(MustNotThrow)} failed. Exception type of '{(ex ?? throw new
ArgumentNullException(nameof(ex))).GetType().Name}' was thrown. This exception was not
expected.", ex);
}
else
{
throw; // seems the test cannot be run properly
...
}
}
}
}
}
There are two overloads, the only difference is, that one takes exactly one exception type to test on, while the other overload can take a bunch of exception types to test on. Mostly you will test on one exception type: Testing on several types of exception could be an indicator, that your test is way too general and you should create a test each exception you want to test on. However, if there is that circumstance that you need to test on multiple exception types - you have the ability (yes you have the same with NUnit when using DoestNotThrow).
You probably have seen that the class is in the same namespace as the Assert type itself. This has been done to ensure, that the test fail diagnose information does filter out our extension method, since this is not what we want to know in case a test fails. The pictures below shows the difference, in case you don't know what I'm talking about:
You see: The extension methods are listed in the stack trace, this is likeley unwanted
This is, what we want to see, in case the test failed.
I don't know if the namespace is also used as filter in other test framework, but try it! And if you have the ugly stack trace like in the first picture: It's not the worlds end, but yes ... it is confusing.
In MSTest project framework, in older versions, Assert type is static, so you cannot use extension methods: In such scenario, just change the extension methods into static methods, that way you can use it even in older framework versions (or rather consider to update the nuget ...).
The test only checks for the exception types you provided, so derived exceptions from the type you provide, will not be tested. This is done that way with the same consideration as you should test exceptions each type with its own test and along with the best practice that exception types shall be sealed, so there are not that much exception derivates expected (besides all derivates from Exception type, of course).
After all this explanations, you may ask yourself: Why do I even need to test for specific exception not to be thrown, if a test method run always fails when an exception is thrown?
Two reasons - One: You just cannot distinguish between the case that you're test have failed or the case your test was not run successfully, because both cases ends in the result, that the test method failes. And if that happens, you have to do further analyzis, what the really source reason was.
Two: When you see Assert.Whatever(...)/Assert.That.Whatever(...) in your test code, you clearly know which code fragement is subject to test with which mechanics.
And for last, a simple example how to use it. Below you can see a very simple test method which shall find out, if the nullReference.ToString() will not raise an exception type of NullReferenceException. As you can guess, the test will fail, because nullReference is null.
using System;
using
Microsoft.VisualStudio.TestTools.UnitTesting;
namespace
UnitTestProject2
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
string nullReference = null;
Assert.That.MustNotThrow<NullReferenceException>(() =>
nullReference.ToString());
}
}
}
And that's the outcome:
Happy testing :)
No comments:
Post a Comment