如何在需要进行模拟和单元测试时抛出SqlException?

cho*_*bo2 72 .net asp.net-mvc moq sqlexception

我试图在我的项目中测试一些异常,并且我遇到的一个例外是SQlException.

看来你不能new SqlException()这样做我不知道怎么能抛出一个异常,特别是没有以某种方式调用数据库(因为这些是单元测试,通常建议不要调用数据库,因为它很慢).

我正在使用NUnit和Moq,但我不确定如何伪造这个.

回应一些似乎都基于ADO.NET的答案,请注意我使用的是Linq to Sql.这样的东西就像在幕后.

根据@MattHamilton的要求提供更多信息:

System.ArgumentException : Type to mock must be an interface or an abstract or non-sealed class.       
  at Moq.Mock`1.CheckParameters()
  at Moq.Mock`1..ctor(MockBehavior behavior, Object[] args)
  at Moq.Mock`1..ctor(MockBehavior behavior)
  at Moq.Mock`1..ctor()
Run Code Online (Sandbox Code Playgroud)

在尝试模型时发布到第一行

 var ex = new Mock<System.Data.SqlClient.SqlException>();
 ex.SetupGet(e => e.Message).Returns("Exception message");
Run Code Online (Sandbox Code Playgroud)

Sam*_*ron 77

你可以用反射做这件事,你必须在微软进行更改时维护它,但它确实有效我只是测试它:

public class SqlExceptionCreator
{
    private static T Construct<T>(params object[] p)
    {
        var ctors = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        return (T)ctors.First(ctor => ctor.GetParameters().Length == p.Length).Invoke(p);
    }

    internal static SqlException NewSqlException(int number = 1)
    {
        SqlErrorCollection collection = Construct<SqlErrorCollection>();
        SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100);

        typeof(SqlErrorCollection)
            .GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance)
            .Invoke(collection, new object[] { error });


        return typeof(SqlException)
            .GetMethod("CreateException", BindingFlags.NonPublic | BindingFlags.Static,
                null,
                CallingConventions.ExplicitThis,
                new[] { typeof(SqlErrorCollection), typeof(string) },
                new ParameterModifier[] { })
            .Invoke(null, new object[] { collection, "7.0.0" }) as SqlException;
    }
}      
Run Code Online (Sandbox Code Playgroud)

这也允许您控制SqlException的数量,这可能很重要.

  • 为了使它能够与dotnet-core 2.0一起使用,请将NewSqlException方法中的第二行更改为:SqlError error = Construct &lt;SqlError&gt;(number,(byte)2,(byte)3,“ server name”, “错误消息”,“ proc”,100,为null); (4认同)
  • 根据评论的更正,变成了一个要点.https://gist.github.com/timabell/672719c63364c497377f - 非常感谢所有人让我走出这个黑暗的黑暗之地. (3认同)
  • 这种方法有效,你需要更具体地使用你想要的CreateException方法,因为有两个重载.将GetMethod调用更改为:.GetMethod("CreateException",BindingFlags.NonPublic | BindingFlags.Static,null,CallingConventions.ExplicitThis,new [] {typeof(SqlErrorCollection),typeof(string)},new ParameterModifier [] {})And有用 (2认同)
  • Ben J Anderson 的版本允许您指定除错误代码之外的消息。https://gist.github.com/benjanderson/07e13d9a2068b32c2911 (2认同)

Dyl*_*tie 61

我有一个解决方案.我不确定这是天才还是疯狂.

以下代码将创建一个新的SqlException:

public SqlException MakeSqlException() {
    SqlException exception = null;
    try {
        SqlConnection conn = new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1");
        conn.Open();
    } catch(SqlException ex) {
        exception = ex;
    }
    return(exception);
}
Run Code Online (Sandbox Code Playgroud)

你可以这样使用(这个例子是使用Moq)

mockSqlDataStore
    .Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
    .Throws(MakeSqlException());
Run Code Online (Sandbox Code Playgroud)

这样您就可以在存储库,处理程序和控制器中测试SqlException错误处理.

现在我需要去躺下.

  • 精彩的解决方案!我对它进行了一次修改以节省一些等待连接的时间:`new SqlConnection(@"Data Source = .; Database = GUARANTEED_TO_FAIL; Connection Timeout = 1") (10认同)
  • 我喜欢您添加到答案中的情感。大声笑谢谢这个解决方案。这是没有道理的,我不知道为什么我最初没有想到这一点。再次感谢。 (2认同)
  • 很好的解决方案,只需确保您的本地计算机上没有名为 GUARANTEED_TO_FAIL 的数据库;) (2认同)

def*_*mer 18

根据具体情况,我通常更喜欢使用GetUninitializedObject来调用ConstructorInfo.您只需要知道它不会调用构造函数 - 来自MSDN备注:"因为对象的新实例初始化为零并且没有运行构造函数,所以该对象可能不代表被视为有效的状态通过那个对象." 但我认为它比依赖某个构造函数的存在要脆弱得多.

[TestMethod]
[ExpectedException(typeof(System.Data.SqlClient.SqlException))]
public void MyTestMethod()
{
    throw Instantiate<System.Data.SqlClient.SqlException>();
}

public static T Instantiate<T>() where T : class
{
    return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(T)) as T;
}
Run Code Online (Sandbox Code Playgroud)

  • 这对我有用,并且一旦你有了对象就设置了异常的消息:`typeof(SqlException).GetField("_ message",BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception,"my custom sql message" );` (3认同)
  • 我扩展了它以反映出ErrorMessage和ErrorCode.https://gist.github.com/benjanderson/07e13d9a2068b32c2911 (3认同)

jjx*_*tra 17

Microsoft.Data.SqlClient

如果您使用新的 Microsoft.Data.SqlClient Nuget 包,则可以使用此帮助器方法:

public static class SqlExceptionCreator
{
    public static SqlException Create(int number)
    {
        Exception? innerEx = null;
        var c = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        SqlErrorCollection errors = (c[0].Invoke(null) as SqlErrorCollection)!;
        var errorList = (errors.GetType().GetField("_errors", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(errors) as List<object>)!;
        c = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        var nineC = c.FirstOrDefault(f => f.GetParameters().Length == 9)!;
        SqlError sqlError = (nineC.Invoke(new object?[] { number, (byte)0, (byte)0, "", "", "", (int)0, (uint)0, innerEx}) as SqlError)!;
        errorList.Add(sqlError);
        SqlException ex = (Activator.CreateInstance(typeof(SqlException), BindingFlags.NonPublic | BindingFlags.Instance, null, new object?[] { "test", errors,
            innerEx, Guid.NewGuid() }, null) as SqlException)!;
        return ex;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这适用于 .NET 6 和 microsoft.data.sqlclient 2.1.4 (3认同)

Mat*_*ton 13

编辑 Ouch:我没有意识到SqlException是密封的.我一直在嘲笑DbException,这是一个抽象类.

您无法创建新的SqlException,但您可以模拟SbException派生自的DbException.试试这个:

var ex = new Mock<DbException>();
ex.ExpectGet(e => e.Message, "Exception message");

var conn = new Mock<SqlConnection>();
conn.Expect(c => c.Open()).Throws(ex.Object);
Run Code Online (Sandbox Code Playgroud)

因此,当方法尝试打开连接时,抛出异常.

如果您希望Message在模拟异常上读取除属性之外的任何内容,那么不要忘记Expect(或安装程序,具体取决于您的Moq版本)这些属性上的"get".


Dal*_*gan 9

由于您使用的是Linq to Sql,以下是使用NUnit和Moq测试您提到的场景的示例.我不知道您的DataContext的确切细节以及您可以使用的内容.根据您的需求进行编辑

您需要使用自定义类包装DataContext,不能使用Moq模拟DataContext.你也不能模拟SqlException,因为它是密封的.您需要使用自己的Exception类包装它.要完成这两件事并不困难.

让我们从创建测试开始:

[Test]
public void FindBy_When_something_goes_wrong_Should_handle_the_CustomSqlException()
{
    var mockDataContextWrapper = new Mock<IDataContextWrapper>();
    mockDataContextWrapper.Setup(x => x.Table<User>()).Throws<CustomSqlException>();

    IUserResository userRespoistory = new UserRepository(mockDataContextWrapper.Object);
    // Now, because we have mocked everything and we are using dependency injection.
    // When FindBy is called, instead of getting a user, we will get a CustomSqlException
    // Now, inside of FindBy, wrap the call to the DataContextWrapper inside a try catch
    // and handle the exception, then test that you handled it, like mocking a logger, then passing it into the repository and verifying that logMessage was called
    User user = userRepository.FindBy(1);
}
Run Code Online (Sandbox Code Playgroud)

让我们实现测试,首先让我们使用存储库模式将Linq包装到Sql调用:

public interface IUserRepository
{
    User FindBy(int id);
}

public class UserRepository : IUserRepository
{
    public IDataContextWrapper DataContextWrapper { get; protected set; }

    public UserRepository(IDataContextWrapper dataContextWrapper)
    {
        DataContextWrapper = dataContextWrapper;
    }

    public User FindBy(int id)
    {
        return DataContextWrapper.Table<User>().SingleOrDefault(u => u.UserID == id);
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来像这样创建IDataContextWrapper,你可以查看关于这个主题的博客帖子,我的有点不同:

public interface IDataContextWrapper : IDisposable
{
    Table<T> Table<T>() where T : class;
}
Run Code Online (Sandbox Code Playgroud)

接下来创建CustomSqlException类:

public class CustomSqlException : Exception
{
 public CustomSqlException()
 {
 }

 public CustomSqlException(string message, SqlException innerException) : base(message, innerException)
 {
 }
}
Run Code Online (Sandbox Code Playgroud)

这是IDataContextWrapper的示例实现:

public class DataContextWrapper<T> : IDataContextWrapper where T : DataContext, new()
{
 private readonly T _db;

 public DataContextWrapper()
 {
        var t = typeof(T);
     _db = (T)Activator.CreateInstance(t);
 }

 public DataContextWrapper(string connectionString)
 {
     var t = typeof(T);
     _db = (T)Activator.CreateInstance(t, connectionString);
 }

 public Table<TableName> Table<TableName>() where TableName : class
 {
        try
        {
            return (Table<TableName>) _db.GetTable(typeof (TableName));
        }
        catch (SqlException exception)
        {
            // Wrap the SqlException with our custom one
            throw new CustomSqlException("Ooops...", exception);
        }
 }

 // IDispoable Members
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*vid 5

不确定这是否有帮助,但似乎对这个人有用(非常聪明)。

try
{
    SqlCommand cmd =
        new SqlCommand("raiserror('Manual SQL exception', 16, 1)",DBConn);
    cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
    string msg = ex.Message; // msg = "Manual SQL exception"
}
Run Code Online (Sandbox Code Playgroud)

位于:http : //smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html