Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# Moq和抛出SqlException_C#_.net_Unit Testing_Mocking_Moq - Fatal编程技术网

C# Moq和抛出SqlException

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

我有以下代码来测试,当某个名称被传递给我的方法时,它会抛出一个SQL异常(这是有原因的,尽管听起来有点奇怪)

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