Я пытаюсь протестировать некоторые исключения в своем проекте, и одно из них, которое я улавливаю, - 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");
.net
asp.net-mvc
moq
sqlexception
chobo2
источник
источник
Ответы:
Поскольку вы используете Linq to Sql, вот образец тестирования упомянутого вами сценария с использованием NUnit и Moq. Я не знаю точных деталей вашего DataContext и того, что у вас есть в нем. Отредактируйте под свои нужды.
Вам нужно будет обернуть DataContext настраиваемым классом, вы не можете имитировать 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 в 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 }
источник
Вы можете сделать это с помощью отражения, вам придется поддерживать его, когда 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, что может быть важно.
источник
NewSqlException
метода следующим образом:SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100, null);
У меня есть решение. Я не уверен, гений это или безумие.
Следующий код создаст новое исключение 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 в своих репозиториях, обработчиках и контроллерах.
Теперь мне нужно пойти и лечь.
источник
new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1")
В зависимости от ситуации я обычно предпочитаю 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; }
источник
typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, "my custom sql message");
Изменить Ой: я не понимал, что 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
свойства в имитируемом исключении, не забудьте ожидать (или настроить, в зависимости от вашей версии Moq) «получить» для этих свойств.источник
Не уверен, что это помогает, но, похоже, сработало для этого человека (довольно умно).
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
источник
Это должно работать:
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();
источник
Я заметил, что вашему вопросу один год, но для записи я хотел бы добавить решение, которое я недавно обнаружил с помощью 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; };
Надеюсь, это поможет кому-то, кто задаст этот вопрос в будущем.
источник
Предлагаю воспользоваться этим методом.
/// <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() }); }
источник
SqlException
что не имеет конструктора иerrorConstructor
будет иметь значение null.Эти решения кажутся раздутыми.
Ctor внутренний, да.
(Без использования отражения, самый простой способ действительно создать это исключение ....
instance.Setup(x => x.MyMethod()) .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());
Perphaps есть еще один метод, для выполнения которого не требуется тайм-аут в 1 секунду.
источник
(Извините, прошло 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());
источник
На основе всех остальных ответов я создал следующее решение:
[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, и я не могу смоделировать абстрактные классы и действительно не хотел использовать отражение, поэтому я создал собственное исключение, производное от DbException. Так:
public class MockDbException : DbException { public MockDbException(string message) : base (message) {} }
очевидно, если вам нужно добавить InnerException или что-то еще, добавьте дополнительные реквизиты, конструкторы и т. д.
тогда в моем тесте:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
Надеюсь, это поможет всем, кто использует Moq. Спасибо всем, кто разместил здесь, что привело меня к моему ответу.
источник
Вы можете использовать отражение для создания объекта 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 });
источник
private SqlException(string message, SqlErrorCollection errorCollection, Exception innerException, Guid conId)