Как бросить исключение SqlException при необходимости для издевательского и модульного тестирования?
Я пытаюсь проверить некоторые исключения в моем проекте, и один из Исключений, которые я улавливаю, - SQlException
.
Кажется, что вы не можете пойти new SqlException()
, поэтому я не уверен, как я могу исключить исключение, особенно если вы каким-то образом не вызываете базу данных (и поскольку это модульные тесты, обычно рекомендуется не вызывать базу данных, поскольку она медленный).
Я использую 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()
Сообщения в первой строке при попытке макета
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
Ответы
Ответ 1
Поскольку вы используете Linq для Sql, вот пример тестирования сценария, который вы упомянули, используя NUnit и Moq. Я не знаю точных данных вашего DataContext и того, что у вас есть в нем. Изменить для ваших нужд.
Вам нужно будет обернуть DataContext специальным классом, вы не можете Mock DataContext с помощью Moq. Вы также не можете издеваться над 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 to 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, так что вы можете просмотреть этот пост в блоге по теме, мой немного отличается:
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
}
Ответ 2
Вы можете сделать это с отражением, вам придется поддерживать его, когда Microsoft вносит изменения, но он работает, я просто протестировал его:
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, которое может быть важным.
Ответ 3
У меня есть решение этого. Я не уверен, это гений или безумие.
Следующий код создаст новое исключение 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 в своих репозиториях, обработчиках и контроллерах.
Теперь мне нужно пойти и лечь.
Ответ 4
В зависимости от ситуации я обычно предпочитаю 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;
}
Ответ 5
Изменить. Ouch: Я не понял, что SqlException запечатан. Я издевался над DbException, который является абстрактным классом.
Вы не можете создать новое исключение 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);
Итак, ваше исключение вызывается, когда метод пытается открыть соединение.
Если вы ожидаете прочитать что-либо, кроме свойства Message
, в издеваемом исключении, не забудьте Expect (или Setup, в зависимости от вашей версии Moq) "получить" эти свойства.
Ответ 6
Это должно работать:
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.
Обновить: нет, второй подход генерирует исключение ArgumentException, а не исключение SqlException.
Обновление 2: это работает намного быстрее (исключение SqlException выбрано менее чем за секунду):
SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial
Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection
Timeout=1");
bogusConn.Open();
Ответ 7
Не уверен, что это помогает, но, похоже, сработало для этого человека (довольно умный).
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
Ответ 8
Я заметил, что ваш вопрос один год, но для записи я хотел бы добавить решение, которое я недавно открыл с помощью microsoft Moles (здесь вы можете найти ссылки Microsoft Moles)
После того, как вы спустили пространство имен System.Data, вы можете просто высмеять исключение SQL в SqlConnection.Open() следующим образом:
//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;
};
Надеюсь, это поможет кому-то, кто обратится к этому вопросу в будущем.
Ответ 9
(Sry его опоздал на 6 месяцев, надеюсь, что это не будет считаться некропостью. Я приземлился здесь, чтобы узнать, как выбросить исключение 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
На основе всех других ответов я создал следующее решение:
[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
Это действительно старо, и здесь есть хорошие ответы. Я использую Moq, и я не могу макетировать абстрактные классы и действительно не хотел использовать отражение, поэтому я сделал свое собственное исключение, полученное из DbException. Итак:
public class MockDbException : DbException {
public MockDbException(string message) : base (message) {}
}
очевидно, если вам нужно добавить InnerException или что-то еще, добавьте дополнительные реквизиты, конструкторы и т.д.
то в моем тесте:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
Ужасно это поможет любому, кто использует Moq. Спасибо всем, кто разместил здесь, что привело меня к моему ответу.
Ответ 12
Вы можете использовать отражение для создания объекта 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 });
Ответ 13
Я предлагаю использовать этот метод.
/// <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() });
}