如何在需要模拟和unit testing时抛出一个SqlException?

我正在尝试在我的项目中testing一些exception,我发现的exception之一是SQlException

看起来你不能去new SqlException()所以我不知道我怎么能抛出一个exception,尤其是没有以某种方式调用数据库(因为这些是unit testing通常build议不要调用数据库,因为它是慢的) 。

我正在使用NUnit和Moq,但我不知道如何伪造。

回答一些似乎都基于ADO.NET的答案,请注意,我正在使用Linq到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() 

当它尝试模拟时,第一行的post

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

由于您正在使用Linq to Sql,下面是使用NUnit和Moqtesting您提到的场景的示例。 我不知道你的DataContext的确切细节和你有什么可用的。 编辑您的需求。

你将需要用自定义的类来包装DataContext,你不能用Moq来模拟DataContext。 你也不能嘲笑SqlException,因为它是封闭的。 你将需要用自己的Exception类来包装它。 要完成这两件事情并不困难。

我们从创build我们的testing开始:

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

让我们来实现这个testing,首先让我们使用存储库模式将我们的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); } } 

接下来像这样创buildIDataContextWrapper,你可以查看这个主题的博客文章 ,我的差异有点:

 public interface IDataContextWrapper : IDisposable { Table<T> Table<T>() where T : class; } 

接下来创buildCustomSqlException类:

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

以下是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 } 

你可以用reflection来做到这一点,当微软做出改变时,你将不得不保持它,但它确实工作,我只是testing它:

 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的数量,这可能是重要的。

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

以下代码将创build一个新的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); } 

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

 mockSqlDataStore .Setup(x => x.ChangePassword(userId, It.IsAny<string>())) .Throws(MakeSqlException()); 

所以你可以testing你的仓库,处理程序和控制器中的SqlExceptionerror handling。

现在我需要去躺下。

根据情况,我通常更喜欢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; } 

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

你不能创build一个新的SqlException,但你可以模拟一个DbException,这个SqlException派生自。 尝试这个:

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

所以当方法试图打开连接时抛出exception。

如果您希望读取模拟exception的Message属性以外的任何内容,请不要忘记Expect(或设置,取决于您的Moq版本)这些属性上的“get”。

不知道这是否有帮助,但似乎已经为这个人(非常聪明)工作。

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

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

我注意到你的问题是一岁,但为了logging,我想添加一个我最近使用微软Moles发现的解决scheme(你可以在这里find微软Moles的参考资料)

一旦你调用了System.Data命名空间,你可以简单地在SqlConnection.Open()上模拟一个SQLexception:

 //Create a delegate for the SqlConnection.Open method of all instances //that raises an error System.Data.SqlClient.Moles.MSqlConnection.AllInstances.Open = (a) => { SqlException myException = new System.Data.SqlClient.Moles.MSqlException(); throw myException; }; 

我希望这能帮助未来打击这个问题的人。

这应该工作:

 SqlConnection bogusConn = new SqlConnection("Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;"); bogusConn.Open(); 

在引发exception之前需要一点时间,所以我认为这样会更快一些:

 SqlCommand bogusCommand = new SqlCommand(); bogusCommand.ExecuteScalar(); 

代码由Hacks-R-Us带给你。

更新 :nope,第二种方法抛出一个ArgumentException,而不是一个SqlException。

更新2 :这工作更快(SqlException抛出不到一秒钟):

 SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection Timeout=1"); bogusConn.Open(); 

(Sry已经晚了6个月,希望这不会被认为是necroposting我在这里寻找如何从模拟抛出一个SqlCeException)。

如果您只需testing处理exception的代码,那么一个超简单的解决方法就是:

 public void MyDataMethod(){ try { myDataContext.SubmitChanges(); } catch(Exception ex) { if(ex is SqlCeException || ex is TestThrowableSqlCeException) { // handle ex } else { throw; } } } public class TestThrowableSqlCeException{ public TestThrowableSqlCeException(string message){} // mimic whatever properties you needed from the SqlException: } var repo = new Rhino.Mocks.MockReposity(); mockDataContext = repo.StrictMock<IDecoupleDataContext>(); Expect.Call(mockDataContext.SubmitChanges).Throw(new TestThrowableSqlCeException()); 

基于所有其他答案,我创build了以下解决scheme:

  [Test] public void Methodundertest_ExceptionFromDatabase_Logs() { _mock .Setup(x => x.MockedMethod(It.IsAny<int>(), It.IsAny<string>())) .Callback(ThrowSqlException); _service.Process(_batchSize, string.Empty, string.Empty); _loggermock.Verify(x => x.Error(It.IsAny<string>(), It.IsAny<SqlException>())); } private static void ThrowSqlException() { var bogusConn = new SqlConnection( "Data Source=localhost;Initial Catalog = myDataBase;User Id = myUsername;Password = myPassword;Connection Timeout = 1"); bogusConn.Open(); } 

这真的很老,这里有一些很好的答案。 我正在使用Moq,我不能嘲笑抽象类,真的不想使用reflection,所以我做了我自己的exception派生从DbException。 所以:

 public class MockDbException : DbException { public MockDbException(string message) : base (message) {} } 

显然,如果你需要添加InnerException,或者其他的,添加更多的道具,构造函数等等。

那么,在我的testing中:

 MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage)); 

好吧,这将有助于任何人使用Moq。 感谢在这里张贴的每个人,让我的答案。

您可以使用reflection在testing中创buildSqlException对象:

  ConstructorInfo errorsCi = typeof(SqlErrorCollection).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[]{}, null); var errors = errorsCi.Invoke(null); ConstructorInfo ci = typeof(SqlException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(SqlErrorCollection) }, null); var sqlException = (SqlException)ci.Invoke(new object[] { "Exception message", errors }); 

我build议使用这种方法。

  /// <summary> /// Method to simulate a throw SqlException /// </summary> /// <param name="number">Exception number</param> /// <param name="message">Exception message</param> /// <returns></returns> public static SqlException CreateSqlException(int number, string message) { var collectionConstructor = typeof(SqlErrorCollection) .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, //visibility null, //binder new Type[0], null); var addMethod = typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance); var errorCollection = (SqlErrorCollection)collectionConstructor.Invoke(null); var errorConstructor = typeof(SqlError).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (int), typeof (byte), typeof (byte), typeof (string), typeof(string), typeof (string), typeof (int), typeof (uint) }, null); var error = errorConstructor.Invoke(new object[] { number, (byte)0, (byte)0, "server", "errMsg", "proccedure", 100, (uint)0 }); addMethod.Invoke(errorCollection, new[] { error }); var constructor = typeof(SqlException) .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, //visibility null, //binder new[] { typeof(string), typeof(SqlErrorCollection), typeof(Exception), typeof(Guid) }, null); //param modifiers return (SqlException)constructor.Invoke(new object[] { message, errorCollection, new DataException(), Guid.NewGuid() }); }