Hy,
I'm writing a class which should help me in unit tests. The class offers methods to perform Assertions on Exceptions.
Until now I was able to write methods which take a function with no parameters and no return value as input. To do this I use the System.Action - delegates. My class looks like this:
internal static class ExceptionAssert
{
/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// <para>
/// The input delegate must be a method with no parameters and return type void!
/// </para>
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute.</param>
public static void Throws<TException>(Action methodToExecute) where TException : System.Exception
{
try
{
methodToExecute();
}
catch (Exception e)
{
Assert.IsTrue(e.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + e.GetType() + " was thrown instead.");
return;
}
Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
}
In the unit test I can write now:
ExceptionAssert.Throws<ArgumentNullException>(theProxy.LoadProduct,productNumber);
Now I want to write more methods, which take methods as input with arguments and return values. As I understand the generic Func should serve this. And the method signature should be like this:
public static void Throws<TException>(Func<T, TResult> methodToExecute, T methodArgument) where TException : System.Exception
But this will not compile. I always have to write an explicit type like Func and not the generic. What's wrong? It should be possible to declare it the generic way, because LINQ works like this.
EDIT:
It is a good idea to declare everything, not just the half. The result of it:
/// <summary>
/// Contains assertion types for exceptions that are not provided with the standard MSTest assertions.
/// </summary>
/// <typeparam name="T">The type of the input arguments.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <remarks>
/// The standard test framework has an Attribute called <see cref="ExpectedExceptionAttribute">ExpectedExceptionAttribute</see>. This attribute has two
/// main disadvantages:
/// <para>
/// 1. The unit test stops at the line which throws the expected exception. If you want to test a method which throws a bunch of exceptions
/// you must write a test for each exception.
/// 2. The attribute does not specify exactly where the exception has to be thrown. So if a method call earlier than expected throws
/// suddenly the same exception, the whole test is still o.k.
/// </para>
/// So this class can be used like the common assertions. You can test a method at a specific line in the test for a specific exception.
/// </remarks>
internal static class ExceptionAssert<T,TResult>
{
/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// <para>
/// The input delegate must be a method with no parameters and return type void!
/// </para>
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute.</param>
public static void Throws<TException>(Action methodToExecute) where TException : System.Exception
{
try
{
methodToExecute();
}
catch (Exception e)
{
Assert.IsTrue(e.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + e.GetType() + " was thrown instead.");
return;
}
Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
}
/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException with a specific exception message.
/// <para>
/// The input delegate must be a method with no parameters and return type void!
/// </para>
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="expectedMessage">The expected exception message.</param>
/// <param name="methodToExecute">The method to execute.</param>
/// <remarks>
/// This method asserts if the given message and the message of the thrown exception are not equal!
/// </remarks>
public static void Throws<TException>(string expectedMessage, Action methodToExecute) where TException : System.Exception
{
try
{
methodToExecute();
}
catch (Exception e)
{
Assert.IsTrue(e.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + e.GetType() + " was thrown instead.");
Assert.AreEqual(expectedMessage, e.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + e.Message + "' was thrown instead.");
return;
}
Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
}
/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException with a specific exception message.
/// <para>
/// The input delegate must be a method with ONE parameter and return type!
/// </para>
/// </summary>
/// <typeparam name="TException">The type of the exception.</typeparam>
/// <param name="methodToExecute">The method to execute.</param>
/// <param name="argument">The argument to input.</param>
public static void Throws<TException>(Func<T,TResult> methodToExecute, T argument)
where TException : System.Exception
{
try
{
methodToExecute(argument);
}
catch (Exception e)
{
Assert.IsTrue(e.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + e.GetType() + " was thrown instead.");
return;
}
Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
}
}