C# Moq和抛出SqlException
我有以下代码来测试,当某个名称被传递给我的方法时,它会抛出一个SQL异常(这是有原因的,尽管听起来有点奇怪)C# Moq和抛出SqlException,c#,.net,unit-testing,mocking,moq,C#,.net,Unit Testing,Mocking,Moq,我有以下代码来测试,当某个名称被传递给我的方法时,它会抛出一个SQL异常(这是有原因的,尽管听起来有点奇怪) mockAccountDAL.Setup(m=>m.CreateAccount(It.IsAny()), “显示名称2”,It.IsAny()).Throws(); 但是,这不会编译,因为SqlException的构造函数是内部的: “System.Data.SqlClient.SqlException”必须是具有 一个公共的无参数构造函数,以便将其用作参数 泛型类型或方法中的“TEx
mockAccountDAL.Setup(m=>m.CreateAccount(It.IsAny()),
“显示名称2”,It.IsAny()).Throws();
但是,这不会编译,因为SqlException的构造函数是内部的:
“System.Data.SqlClient.SqlException”必须是具有
一个公共的无参数构造函数,以便将其用作参数
泛型类型或方法中的“TException”
'Moq.Language.IThrows.Throws()'
现在,我可以将其更改为声明它应该抛出异常
,但这对我来说不起作用,因为如果它是SqlException
,我的方法应该返回一个状态代码,如果它是任何其他异常,则返回另一个状态代码。这就是我的单元测试要测试的
在不改变我正在测试的方法的逻辑或不测试此场景的情况下,有没有办法实现这一点?这应该可以:
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);
使用System.Runtime.Serialization;
var exception=FormatterServices.GetUninitializedObject(typeof(SqlException))
作为例外;
mockAccountDAL.Setup(m=>m.CreateAccount(It.IsAny(),“显示名称2”,
It.IsAny()).Throws(异常);
但是,使用GetUninitializedObject
有以下警告:
因为对象的新实例已初始化为零,并且没有
如果运行构造函数,则对象可能不表示
被那个物体认为是有效的
如果这会导致任何问题,您可能可以使用一些更复杂的反射魔法来创建它,但这种方法可能是最简单的(如果可行的话)。我刚刚尝试过这个方法,它对我很有效:
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());
private static void ThrowSqlException()
{
使用(var cxn=newsqlconnection(“连接超时=1”))
{
cxn.Open();
}
}
// ...
mockAccountDAL.Setup(m=>m.CreateAccount(It.IsAny),
“显示名称2”,It.IsAny())
.Callback(()=>ThrowSqlException());
如果您需要异常的编号
或消息
属性的测试用例,您可以使用如下生成器(使用反射):
using System;
using System.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); // 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;
}
}
然后,您可以让一个存储库模拟(例如)抛出这样的异常(本例使用Moq
库):
使用Moq;
var sqlException=
新的SqlExceptionBuilder().WithErrorNumber(50000)
.WithErrorMessage(“发生数据库异常…”)
.Build();
var repoStub=new Mock();//或者别的什么。。。
repoStub.Setup(stub=>stub.GetById(1))
.Throws(sqlException);
对于我来说,生成带有消息的SqlException
是使用未初始化对象方法的最简单方法:
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);
在找到这个问题/答案之前,我写了这篇文章。对于只需要特定数字的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;
}
您可以使用反射来访问内部方法CreateException,该方法允许您创建SQLException。。。然后做一个lamda来创建并抛出它。如果
CreateAccount
返回void怎么办?现在我想起来了,可能在任何情况下都想使用.Callback
。更新答案。没有办法设置异常消息,对吗?@bump可能通过反射实现,但取决于底层结构,这可能非常困难(即获取属性的支持字段并进行设置)。我不确定消息会给你什么设置,除非你有不同的逻辑根据异常消息的内容运行,你需要测试它。我确实需要测试它。能够通过反射(和私有构造函数)实现。谢谢。有关设置了Number
和Message
属性的SqlException
示例,请参见我的答案。它很有效™, 但由此产生的SqlException
功能失调。例如,尝试对其调用.ToString(),您可能不喜欢结果。谢谢!!:D@StephanRyer我有密码。。。我只是觉得你是个绅士和学者。你需要把这个放到Github上,这样我就可以启动它了。如果使用,您需要将GetParameters.Count()
更改为8,并将new Exception()
添加到参数列表中这是最全面的答案,符合问题->sql异常和单元测试背后的原始动机。很好的答案-对于构建器模式还有额外的要点。
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);
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;
}