我正在尝试在我的项目中测试一些异常,其中一个例外是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()
尝试模拟时发布到第一行
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
答案 0 :(得分:75)
你可以用反射做到这一点,你必须在微软进行更改时维护它,但它确实有效我只是测试了它:
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的数量,这可能很重要。
答案 1 :(得分: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);
}
然后您可以这样使用(此示例使用Moq)
mockSqlDataStore
.Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
.Throws(MakeSqlException());
这样您就可以在存储库,处理程序和控制器中测试SqlException错误处理。
现在我需要去躺下。
答案 2 :(得分:17)
根据具体情况,我通常更喜欢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;
}
答案 3 :(得分:13)
编辑哎哟:我没有意识到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);
因此,当方法尝试打开连接时,抛出异常。
如果您希望在模拟异常上读取除Message
属性以外的任何内容,请不要忘记Expect(或安装程序,具体取决于您的Moq版本)对这些属性的“获取”。
答案 4 :(得分: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);
}
让我们实现测试,首先让我们使用存储库模式将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);
}
}
接下来像这样创建IDataContextWrapper,你可以在这个主题上查看这个blog post,我的有点不同:
public interface IDataContextWrapper : IDisposable
{
Table<T> Table<T>() where T : class;
}
接下来创建CustomSqlException类:
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
}
答案 5 :(得分:2)
这应该有效:
SqlConnection bogusConn =
new SqlConnection("Data Source=myServerAddress;Initial
Catalog=myDataBase;User Id=myUsername;Password=myPassword;");
bogusConn.Open();
在它抛出异常之前需要一点点,所以我认为这样可以更快地运行:
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();
答案 6 :(得分:2)
不确定这是否有帮助,但似乎对这个人有用(非常聪明)。
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"
}
发现于: http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html
答案 7 :(得分:2)
我注意到你的问题已经有一年了,但是为了记录,我想添加一个我最近使用microsoft Moles发现的解决方案(你可以在这里找到引用Microsoft Moles)
一旦你使用了System.Data命名空间,就可以像这样在SqlConnection.Open()上模拟一个SQL异常:
//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;
};
我希望这可以帮助将来遇到这个问题的人。
答案 8 :(得分:2)
这些解决方案感到肿。
ctor是内部的,是的。
(不使用反射,这是真正创建此异常的最简单方法...。
instance.Setup(x => x.MyMethod())
.Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());
表示,还有另一种方法不需要一秒钟的超时就可以抛出。
答案 9 :(得分:1)
(Sry已经晚了6个月,希望这不会被视为necroposting我在这里寻找如何从模拟中抛出SqlCeException)。
如果您只需要测试处理异常的代码,那么超简单的解决方法就是:
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());
答案 10 :(得分:1)
基于所有其他答案,我创建了以下解决方案:
[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();
}
答案 11 :(得分:1)
这真的很古老,这里有一些很好的答案。我正在使用Moq,我无法模拟抽象类,并且真的不想使用反射,所以我创建了自己的Exception派生自DbException。所以:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
显然,如果你需要添加InnerException,或者其他什么,可以添加更多的道具,构造函数等。
然后,在我的测试中:
{{1}}
这可以帮助任何使用Moq的人。感谢所有发布在这里的人,让我得到了答案。
答案 12 :(得分:1)
我建议使用这种方法。
int numberofmove = steps.count();
答案 13 :(得分:1)
如果您使用的是新的 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;
}
}
答案 14 :(得分:0)
您可以使用反射在测试中创建SqlException对象:
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 });
答案 15 :(得分:0)
我只成功使用@jjxtra 的方法(我赞成),但是需要修改代码,因为我使用的是 System.Data.SqlClient,它没有 SqlError 的 9 参数构造函数,并且 SqlErrorCollection 有一个名为的字段“errors”(不是“_errors”)是 ArrayList 类型(不是 List
// 程序集位置:C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Data.dll
这是对我有用的修改后的代码:
public static SqlException CreateSqlException(int number)
{
Exception? innerEx = null;
var c = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
SqlErrorCollection errors = (c[0].Invoke(null) as SqlErrorCollection);
ArrayList errorList = (ArrayList)errors.GetType().GetField("errors", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(errors);
c = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
var theC = c.FirstOrDefault(f => f.GetParameters().Length == 8);
SqlError sqlError = (theC.Invoke(new object?[] { number, (byte)0, (byte)0, "", "", "", (int)0, (uint)0}) 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;
}