Moq and throwing a SqlException

对着背影说爱祢 提交于 2019-11-30 01:56:11
docmanhattan

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).

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;
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); // Need a specific constructor!
        SqlError error = firstSqlErrorCtor.Invoke(
            new object[] 
            { 
                this.errorNumber, 
                new byte(), 
                new byte(), 
                string.Empty, 
                string.Empty, 
                string.Empty, 
                new int() 
            }) 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:

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);

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());

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!