Подтвердить что ты не робот

Moq и исключение SqlException

У меня есть следующий код для проверки того, что, когда определенное имя передается моему методу, оно генерирует исключение SQL (есть причина для этого, хотя это звучит немного странно).

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

Однако это не будет скомпилировано, потому что конструктор SqlException является внутренним:

'System.Data.SqlClient.SqlException' должен быть не абстрактный тип с открытый конструктор без параметров, чтобы использовать его в качестве параметра "TException" в родовом типе или методе 'Moq.Language.IThrows.Throws()'

Теперь я могу изменить это, чтобы указать, что он должен бросать Exception, но это не сработает для меня, потому что мой метод должен возвращать один код состояния, если он является SqlException, а другой, если это какой-либо другой исключение. Это то, что тестирует мой unit test.

Есть ли способ достичь этого, не изменяя логику метода, который я тестирую, или не тестировал этот сценарий?

4b9b3361

Ответ 1

Это должно работать:

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

Однако использование GetUninitializedObject имеет это оговорку:

Поскольку новый экземпляр объекта инициализируется нулем и нет выполняются конструкторы, объект может не представлять состояние, которое считается действительным для этого объекта.

Если это вызывает какие-либо проблемы, возможно, вы можете создать его, используя еще одну магию отражения, но этот путь, вероятно, самый простой (если он работает).

Ответ 2

Если вам нужны тестовые случаи для свойств Number или Message исключения, вы можете использовать построитель (который использует отражение), например:

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

Тогда вы могли бы сделать, чтобы макет репозитория (например) выдавал исключение следующим образом:

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

Ответ 3

Я просто попробовал это, и это сработало для меня:

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

Ответ 4

Для меня, чтобы создать SqlException с сообщением, это был самый простой способ, используя метод Uninitialized Object:

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