45

I have the following code to test that when a certain name is passed to my method, it throws a SQL exception (there is reason to that one, although it sounds a little odd).

   mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), 
"Display Name 2", It.IsAny<string>())).Throws<SqlException>();

However, this won't compile because SqlException's constructor is internal:

'System.Data.SqlClient.SqlException' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TException' in the generic type or method 'Moq.Language.IThrows.Throws()'

Now, I could change this to state that it should throw Exception, but that wouldn't work for me, because my method should return one status code if it is a SqlException and another if it is any other exception. That's what my unit test is testing.

Is there any way to achieve this without either changing the logic of the method I'm testing, or not testing this scenario?

SteveC
  • 15,808
  • 23
  • 102
  • 173
Fiona - myaccessible.website
  • 14,481
  • 16
  • 82
  • 117
  • 1
    possible duplicate of [How to throw a SqlException(need for mocking)](http://stackoverflow.com/questions/1386962/how-to-throw-a-sqlexceptionneed-for-mocking) – Joachim Isaksson Aug 15 '12 at 21:12
  • 1
    You can use Reflection to access the internal method CreateException that lets you create a SQLException. http://stackoverflow.com/questions/1259222/how-to-access-internal-class-using-reflection .... http://msdn.microsoft.com/en-us/library/ms229394(v=vs.100).aspx ... then just do a lamda to create and throw it. – Colin Smith Aug 15 '12 at 21:21

6 Answers6

76

If you need test cases for the Number or Message properties of the exception, you could use a builder (which uses reflection) like this:

using System;
using System.Data.SqlClient;  // .NetCore using Microsoft.Data.SqlClient;
using System.Linq;
using System.Reflection;

public class SqlExceptionBuilder
{
    private int errorNumber;
    private string errorMessage;

    public SqlException Build()
    {
        SqlError error = this.CreateError();
        SqlErrorCollection errorCollection = this.CreateErrorCollection(error);
        SqlException exception = this.CreateException(errorCollection);

        return exception;
    }

    public SqlExceptionBuilder WithErrorNumber(int number)
    {
        this.errorNumber = number;
        return this;
    }

    public SqlExceptionBuilder WithErrorMessage(string message)
    {
        this.errorMessage = message;
        return this;
    }

    private SqlError CreateError()
    {
        // Create instance via reflection...
        var ctors = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        var firstSqlErrorCtor = ctors.FirstOrDefault(
            ctor =>
            ctor.GetParameters().Count() == 7); // .NetCore should be 8 not 7
        SqlError error = firstSqlErrorCtor.Invoke(
            new object[] 
            { 
                this.errorNumber, 
                new byte(), 
                new byte(), 
                string.Empty, 
                string.Empty, 
                string.Empty, 
                new int() 
            //,new Exception()  // for .NetCore 
            }) as SqlError;

        return error;
    }
 
    private SqlErrorCollection CreateErrorCollection(SqlError error)
    {
        // Create instance via reflection...
        var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        SqlErrorCollection errorCollection = sqlErrorCollectionCtor.Invoke(new object[] { }) as SqlErrorCollection;

        // Add error...
        typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(errorCollection, new object[] { error });

        return errorCollection;
    }

    private SqlException CreateException(SqlErrorCollection errorCollection)
    {
        // Create instance via reflection...
        var ctor = typeof(SqlException).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        SqlException sqlException = ctor.Invoke(
            new object[] 
            { 
                // With message and error collection...
                this.errorMessage, 
                errorCollection,
                null,
                Guid.NewGuid() 
            }) as SqlException;

        return sqlException;
    }
}

Then you could have a repository mock (for instance) throw an exception like this (this example uses the Moq library):

using Moq;

var sqlException = 
    new SqlExceptionBuilder().WithErrorNumber(50000)
        .WithErrorMessage("Database exception occured...")
        .Build();
var repoStub = new Mock<IRepository<Product>>(); // Or whatever...
repoStub.Setup(stub => stub.GetById(1))
    .Throws(sqlException);
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
Aage
  • 5,932
  • 2
  • 32
  • 57
  • 1
    Thank you for this !! :D – Stephan Møller Aug 20 '15 at 12:06
  • @StephanRyer I had the code... just thought I'd share – Aage Aug 25 '15 at 08:34
  • 1
    You're a gentleman and a scholar. You need to get this up on Github so I can star it. – Matthew Mark Miller Apr 20 '16 at 04:05
  • 14
    came across this gem. If using [corefx](https://github.com/dotnet/corefx/blob/master/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlError.cs), you'll need to change the `GetParameters.Count()` to 8 and add `new Exception()` to the parameter list – jmzagorski Nov 04 '16 at 18:11
  • 6
    This is the most comprehensive answer and lends itself to the original motivation behind the question -> sql exceptions and unit tests. Great answer - and extra points for builder pattern. – adelpreore Feb 23 '17 at 17:20
  • Best answer I've found, also had to change the code slightly as per @jmzagorski comment. – Crwydryn Sep 22 '20 at 14:03
  • I just tried to use this and although it's a great solution it needs a small update! Currently the two ctors for SqlError take 8 or 9 parameters. I changed it to 8 and used the following signature: Int32, Byte, Byte, System.String, System.String, System.String, Int32, System.Exception. This is in CreateError above. – ScottyMacDev Feb 20 '22 at 06:24
58

This should work:

using System.Runtime.Serialization;

var exception = FormatterServices.GetUninitializedObject(typeof(SqlException)) 
                as SqlException;

mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), "Display Name 2", 
                     It.IsAny<string>())).Throws(exception);

However, using GetUninitializedObject has this caveat:

Because the new instance of the object is initialized to zero and no constructors are run, the object might not represent a state that is regarded as valid by that object.

If this causes any problems, you can probably create it using some more involved reflection magic but this way is probably the simplest (if it works).

SteveC
  • 15,808
  • 23
  • 102
  • 173
docmanhattan
  • 2,368
  • 1
  • 25
  • 28
  • There wouldn't be a way to set the exception message, would there? – Aage Apr 16 '15 at 11:42
  • @bump It might be possible via reflection but depending on the underlying structure it could be pretty hard to do (ie getting the backing fields of the properties and setting those). I'm not sure what setting the message gives you, unless you have different logic running based on what the exception message is stating and you need to test that. – docmanhattan Apr 16 '15 at 17:56
  • I indeed needed to test that. Was able to do it via reflection (and the private constructor). Thanks. – Aage Apr 21 '15 at 06:48
  • 1
    See my answer for an example of a `SqlException` with `Number` and `Message` properties set. – Aage Apr 29 '15 at 09:33
  • It works™, but the `SqlException` resulting from that is dysfunctional. For example, try calling .ToString() on it, and you might not like the result. – Tipx Nov 28 '17 at 21:22
7

I just tried this out, and it worked for me:

private static void ThrowSqlException()
{
    using (var cxn = new SqlConnection("Connection Timeout=1"))
    {
        cxn.Open();
    }
}

// ...
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>),
                     "Display Name 2", It.IsAny<string>()))
              .Callback(() => ThrowSqlException());
Steve Czetty
  • 6,147
  • 9
  • 39
  • 48
2

For me to produce an SqlException with a message it was the simplest way using the Uninitialized Object method:

const string sqlErrorMessage = "MyCustomMessage";
var sqlException = FormatterServices.GetUninitializedObject(typeof(SqlException)) as SqlException;
var messageField = typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance);
messageField.SetValue(sqlException, sqlErrorMessage);
ther
  • 84
  • 4
0

I wrote this before finding this question/answer. Might be useful for someone just wanting a SQL exception with a particular number.

private static SqlException CreateSqlExceptionWithNumber(int errorNumber)
{
    var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[0],
        null);

    var sqlErrorCollection = (SqlErrorCollection)sqlErrorCollectionCtor.Invoke(new object[0]);

    var errors = new ArrayList();

    var sqlError = (SqlError)FormatterServices.GetSafeUninitializedObject(typeof(SqlError));

    typeof(SqlError)
        .GetField("number", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.SetValue(sqlError, errorNumber);

    errors.Add(sqlError);

    typeof(SqlErrorCollection)
        .GetField("errors", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.SetValue(sqlErrorCollection, errors);

    var exception = (SqlException)FormatterServices.GetUninitializedObject(typeof(SqlException));

    typeof(SqlException)
        .GetField("_errors", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.SetValue(exception, sqlErrorCollection);
    
    return exception;
}
Cr1spy
  • 241
  • 1
  • 5
0

public class SqlExceptionMock { public static SqlException ThrowSqlException(int errorNumber, string message = null) { var ex = (SqlException)FormatterServices.GetUninitializedObject(typeof(SqlException)); var errors = GenerateSqlErrorCollection(errorNumber, message); SetPrivateFieldValue(ex, "_errors", errors); return ex; }

    private static SqlErrorCollection GenerateSqlErrorCollection(int errorNumber, string message)
    {
        var t = typeof(SqlErrorCollection);
        var col = (SqlErrorCollection)FormatterServices.GetUninitializedObject(t);
        SetPrivateFieldValue(col, "_errors", new List<object>());
        var sqlError = GenerateSqlError(errorNumber, message);
        var method = t.GetMethod(
          "Add",
          BindingFlags.NonPublic | BindingFlags.Instance);
        method.Invoke(col, new object[] { sqlError });
        return col;
    }

    private static SqlError GenerateSqlError(int errorNumber, string message)
    {
        var sqlError = (SqlError)FormatterServices.GetUninitializedObject(typeof(SqlError));

        SetPrivateFieldValue(sqlError, "_number", errorNumber);
        if (!string.IsNullOrEmpty(message)) SetPrivateFieldValue(sqlError, "_message", message);
        return sqlError;
    }

    private static void SetPrivateFieldValue(object obj, string field, object val)
    {
        var member = obj.GetType().GetField(
          field,
          System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
          );
        member?.SetValue(obj, val);
    }
}
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 10 '23 at 02:17