我有以下代码来测试当某个名称传递给我的方法时,它会抛出一个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
而另一个是这是任何其他例外。这就是我的单元测试正在测试的内容。
有没有办法实现这一点,既没有改变我正在测试的方法的逻辑,也没有测试这种情况?
答案 0 :(得分:43)
这应该有效:
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
有一个警告:
因为对象的新实例初始化为零而没有 运行构造函数,对象可能不代表状态 被该物体视为有效。
如果这会导致任何问题,你可以使用一些更复杂的反射魔法创建它,但这种方式可能是最简单的(如果它有效)。
答案 1 :(得分:38)
如果您需要异常的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;
}
}
然后你可以有一个存储库mock(例如)抛出这样的异常:
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);
答案 2 :(得分:5)
我刚尝试了这个,它对我有用:
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());
答案 3 :(得分:0)
对我来说,生成带有消息的End Sub
是使用未初始化对象方法的最简单方法:
SqlException
答案 4 :(得分:0)
我在找到这个问题/答案之前写了这个。对于只想要具有特定数字的 SQL 异常的人来说可能很有用。
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;
}