Próbuję przetestować kilka wyjątków w moim projekcie i jeden z wyjątków, które łapię, to SQlException
.
Wygląda na to, że nie możesz przejść, new SqlException()
więc nie jestem pewien, jak mogę zgłosić wyjątek, zwłaszcza bez wywoływania bazy danych (a ponieważ są to testy jednostkowe, zwykle odradza się wywoływanie bazy danych, ponieważ jest wolna).
Używam NUnit i Moq, ale nie wiem, jak to udawać.
Odpowiadając na niektóre odpowiedzi, które wydają się być oparte na ADO.NET, zauważ, że używam Linq to Sql. Więc to jest jak za kulisami.
Więcej informacji na prośbę @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()
Wysyła do pierwszego wiersza, gdy próbuje wykonać makietę
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
.net
asp.net-mvc
moq
sqlexception
chobo2
źródło
źródło
Odpowiedzi:
Ponieważ używasz Linq do Sql, oto próbka testowania wspomnianego scenariusza przy użyciu NUnit i Moq. Nie znam dokładnych szczegółów Twojego DataContext i tego, co w nim masz. Edytuj według swoich potrzeb.
Będziesz musiał opakować DataContext klasą niestandardową, nie możesz Mockować DataContext za pomocą Moq. Nie można również mock SqlException, ponieważ jest on zapieczętowany. Będziesz musiał opakować go własną klasą wyjątków. Osiągnięcie tych dwóch rzeczy nie jest trudne.
Zacznijmy od stworzenia naszego testu:
[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); }
Zaimplementujmy test, najpierw zawińmy nasze wywołania Linq do Sql przy użyciu wzorca repozytorium:
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); } }
Następnie utwórz IDataContextWrapper w ten sposób, możesz wyświetlić ten post na blogu na ten temat, mój trochę się różni:
public interface IDataContextWrapper : IDisposable { Table<T> Table<T>() where T : class; }
Następnie utwórz klasę CustomSqlException:
public class CustomSqlException : Exception { public CustomSqlException() { } public CustomSqlException(string message, SqlException innerException) : base(message, innerException) { } }
Oto przykładowa implementacja 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 }
źródło
Możesz to zrobić z refleksją, będziesz musiał to utrzymywać, gdy Microsoft wprowadzi zmiany, ale działa, właśnie to przetestowałem:
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; } }
Umożliwia to również kontrolowanie numeru SqlException, który może być ważny.
źródło
NewSqlException
metodzie na:SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100, null);
Mam na to rozwiązanie. Nie jestem pewien, czy to geniusz czy szaleństwo.
Poniższy kod utworzy nowy 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); }
którego możesz następnie użyć w ten sposób (w tym przykładzie jest używany Moq)
mockSqlDataStore .Setup(x => x.ChangePassword(userId, It.IsAny<string>())) .Throws(MakeSqlException());
aby można było przetestować obsługę błędów SqlException w repozytoriach, programach obsługi i kontrolerach.
Teraz muszę iść i się położyć.
źródło
new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1")
W zależności od sytuacji zwykle wolę GetUninitializedObject od wywoływania ConstructorInfo. Musisz tylko mieć świadomość, że nie wywołuje konstruktora - z MSDN Uwagi: „Ponieważ nowe wystąpienie obiektu jest inicjowane na zero i nie są uruchamiane żadne konstruktory, obiekt może nie reprezentować stanu, który jest uważany za prawidłowy przez ten obiekt. " Ale powiedziałbym, że jest mniej kruchy niż poleganie na istnieniu pewnego konstruktora.
[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; }
źródło
typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, "my custom sql message");
Edytuj Ouch: Nie zdawałem sobie sprawy, że SqlException jest zapieczętowany. Kpiłem z DbException, który jest klasą abstrakcyjną.
Nie można utworzyć nowego SqlException, ale można mockować DbException, z którego SqlException pochodzi. Spróbuj tego:
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);
Więc twój wyjątek jest generowany, gdy metoda próbuje otworzyć połączenie.
Jeśli spodziewasz się odczytać cokolwiek innego niż
Message
właściwość w symulowanym wyjątku, nie zapomnij Oczekiwania (lub Instalatora, w zależności od wersji Moq) polecenia „pobierz” dla tych właściwości.źródło
Nie jestem pewien, czy to pomaga, ale wydaje się, że zadziałało dla tej osoby (całkiem sprytne).
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" }
Znalezione pod adresem : http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html
źródło
To powinno działać:
SqlConnection bogusConn = new SqlConnection("Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;"); bogusConn.Open();
Trochę to potrwa, zanim zgłosi wyjątek, więc myślę, że działałoby to jeszcze szybciej:
SqlCommand bogusCommand = new SqlCommand(); bogusCommand.ExecuteScalar();
Kod dostarczony przez Hacks-R-Us.
Aktualizacja : nie, drugie podejście zgłasza ArgumentException, a nie SqlException.
Aktualizacja 2 : to działa znacznie szybciej (SqlException jest rzucany w mniej niż sekundę):
SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection Timeout=1"); bogusConn.Open();
źródło
Zauważyłem, że twoje pytanie ma już rok, ale dla przypomnienia chciałbym dodać rozwiązanie, które odkryłem niedawno przy użyciu Microsoft Moles (możesz znaleźć odniesienia tutaj Microsoft Moles )
Gdy już spolujesz przestrzeń nazw System.Data, możesz po prostu mockować wyjątek SQL w SqlConnection.Open () w następujący sposób:
//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; };
Mam nadzieję, że pomoże to komuś, kto odpowie na to pytanie w przyszłości.
źródło
Te rozwiązania wydają się rozdęte.
Ktor jest wewnętrzny, tak.
(Bez używania refleksji, najłatwiejszy sposób na autentyczne utworzenie tego wyjątku ....
instance.Setup(x => x.MyMethod()) .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());
Perphaps jest jeszcze jedna metoda, która nie wymaga limitu czasu 1 sekundy na rzucenie.
źródło
(Sry, jest spóźnienie 6 miesięcy, mam nadzieję, że to nie będzie uważane za nekropostę. Wylądowałem tutaj, szukając, jak rzucić SqlCeException z próby).
Jeśli potrzebujesz tylko przetestować kod, który obsługuje wyjątek, bardzo prostym obejściem byłoby:
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());
źródło
Na podstawie wszystkich innych odpowiedzi stworzyłem następujące rozwiązanie:
[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(); }
źródło
To jest naprawdę stare i jest tutaj kilka dobrych odpowiedzi. Używam Moq i nie mogę zrobić makiety z klas abstrakcyjnych i naprawdę nie chciałem używać odbicia, więc stworzyłem własny wyjątek pochodzący z DbException. Więc:
public class MockDbException : DbException { public MockDbException(string message) : base (message) {} }
oczywiście, jeśli chcesz dodać InnerException lub cokolwiek innego, dodaj więcej właściwości, konstruktorów itp.
następnie w moim teście:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
Hoepfully pomoże to każdemu, kto używa Moq. Dziękuję wszystkim, którzy tutaj napisali, co doprowadziło mnie do mojej odpowiedzi.
źródło
Proponuję skorzystać z tej metody.
/// <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() }); }
źródło
SqlException
nie ma konstruktora ierrorConstructor
będzie miało wartość null.Możesz użyć odbicia, aby utworzyć obiekt SqlException w teście:
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 });
źródło
private SqlException(string message, SqlErrorCollection errorCollection, Exception innerException, Guid conId)