首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在需要模拟和单元测试时抛出SqlException?

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

Stack Overflow用户
提问于 2009-09-06 23:11:43
回答 16查看 70.1K关注 0票数 93

我正在尝试测试我的项目中的一些异常,我捕获的异常之一是SQlException

似乎不能使用new SqlException(),所以我不确定如何抛出异常,特别是在不调用数据库的情况下(因为这些是单元测试,所以通常建议不要调用数据库,因为它很慢)。

我正在使用NUnit和Moq,但我不确定如何伪造它。

在回答一些似乎都基于ADO.NET的问题时,请注意我使用的是Linq to Sql。所以这些东西就像是幕后的东西。

@MattHamilton要求提供更多信息:

代码语言:javascript
复制
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()

当它尝试模拟模型时,将帖子放到第一行

代码语言:javascript
复制
 var ex = new Mock<System.Data.SqlClient.SqlException>();
 ex.SetupGet(e => e.Message).Returns("Exception message");
EN

回答 16

Stack Overflow用户

回答已采纳

发布于 2009-09-07 06:22:31

由于您使用的是Linq to Sql,下面是使用NUnit和Moq测试您提到的场景的示例。我不知道你的DataContext的确切细节,也不知道你有什么可用的。根据您的需要进行编辑。

你需要用一个自定义的类来包装DataContext,你不能用Moq来模拟DataContext。您也不能模拟SqlException,因为它是密封的。你需要用你自己的异常类来包装它。完成这两件事并不难。

让我们从创建测试开始:

代码语言:javascript
复制
[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);
}

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

代码语言:javascript
复制
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);
    }
}

接下来像这样创建IDataContextWrapper,您可以在主题上查看此blog post,我的略有不同:

代码语言:javascript
复制
public interface IDataContextWrapper : IDisposable
{
    Table<T> Table<T>() where T : class;
}

接下来创建CustomSqlException类:

代码语言:javascript
复制
public class CustomSqlException : Exception
{
 public CustomSqlException()
 {
 }

 public CustomSqlException(string message, SqlException innerException) : base(message, innerException)
 {
 }
}

下面是IDataContextWrapper的一个示例实现:

代码语言:javascript
复制
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
}
票数 9
EN

Stack Overflow用户

发布于 2009-09-06 23:44:45

你可以通过反射来做到这一点,当微软进行更改时,你必须维护它,但它确实有效,我刚刚测试了它:

代码语言:javascript
复制
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;
    }
}      

这还允许您控制SqlException的数量,这一点非常重要。

票数 95
EN

Stack Overflow用户

发布于 2011-05-21 00:44:14

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

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

代码语言:javascript
复制
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);
}

然后你可以像这样使用它(这个例子使用的是Moq)

代码语言:javascript
复制
mockSqlDataStore
    .Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
    .Throws(MakeSqlException());

以便您可以在存储库、处理程序和控制器中测试SqlException错误处理。

现在我要去躺着了。

票数 84
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1386962

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档