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.
Есть ли способ достичь этого, не изменяя логику метода, который я тестирую, или не тестировал этот сценарий?
Ответы
Ответ 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);