.net Moq并抛出SqlException

zlwx9yxi  于 2023-01-10  发布在  .NET
关注(0)|答案(6)|浏览(140)

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

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

但是,这不会编译,因为SqlException的构造函数是内部的:
“System.Data.SqlClient.SqlException”必须是具有公共无参数构造函数的非抽象类型,才能将其用作泛型类型或方法“Moq.Language.IThrows.Throws()”中的参数“TException”
现在,我可以将其更改为应该抛出Exception,但这对我不起作用,因为如果是SqlException,我的方法应该返回一个状态代码,如果是其他异常,则返回另一个状态代码,这就是我的单元测试所测试的。
有没有什么方法可以在不改变我正在测试的方法的逻辑或者不测试这个场景的情况下实现这一点?

axzmvihb

axzmvihb1#

如果您需要异常的NumberMessage属性的测试用例,您可以使用构建器(使用反射),如下所示:

using System;
using System.Data.SqlClient;  // .NetCore using Microsoft.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); // .NetCore should be 8 not 7
        SqlError error = firstSqlErrorCtor.Invoke(
            new object[] 
            { 
                this.errorNumber, 
                new byte(), 
                new byte(), 
                string.Empty, 
                string.Empty, 
                string.Empty, 
                new int() 
            //,new Exception()  // for .NetCore 
            }) 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库):

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

u1ehiz5o2#

这应该行得通:

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有以下警告:
由于对象的新示例初始化为零,并且没有运行构造函数,因此该对象可能不表示该对象视为有效的状态。
如果这导致了任何问题,您可能可以使用一些更复杂的反射魔术来创建它,但这种方式可能是最简单的(如果它有效的话)。

c9qzyr3d

c9qzyr3d3#

我刚试过这个,对我很有效:

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

v1l68za44#

对于我来说,使用Uninitialized Object方法生成一个带有消息的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);
q43xntqr

q43xntqr5#

我在找到这个问题/答案之前写了这个。对于只想要一个特定数字的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;
}
tktrz96b

tktrz96b6#

返回值为0 {var ex =(SqlException)格式化服务.GetUninitializedObject(类型为(SqlExceptionMock));返回值为0 { var ex =(SqlException)(返回值为(SqlException); var errors =生成SqlErrorCollection(错误编号,消息);设置私有字段值(例如,“_errors”,错误);返回ex; }

private static SqlErrorCollection GenerateSqlErrorCollection(int errorNumber, string message)
    {
        var t = typeof(SqlErrorCollection);
        var col = (SqlErrorCollection)FormatterServices.GetUninitializedObject(t);
        SetPrivateFieldValue(col, "_errors", new List<object>());
        var sqlError = GenerateSqlError(errorNumber, message);
        var method = t.GetMethod(
          "Add",
          BindingFlags.NonPublic | BindingFlags.Instance);
        method.Invoke(col, new object[] { sqlError });
        return col;
    }

    private static SqlError GenerateSqlError(int errorNumber, string message)
    {
        var sqlError = (SqlError)FormatterServices.GetUninitializedObject(typeof(SqlError));

        SetPrivateFieldValue(sqlError, "_number", errorNumber);
        if (!string.IsNullOrEmpty(message)) SetPrivateFieldValue(sqlError, "_message", message);
        return sqlError;
    }

    private static void SetPrivateFieldValue(object obj, string field, object val)
    {
        var member = obj.GetType().GetField(
          field,
          System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
          );
        member?.SetValue(obj, val);
    }
}

相关问题