Estoy tratando de probar algunas excepciones en mi proyecto y una de las excepciones que capturo es SQlException
.
Parece que no puede ir, new SqlException()
así que no estoy seguro de cómo puedo lanzar una excepción, especialmente sin llamar de alguna manera a la base de datos (y dado que estas son pruebas unitarias, generalmente se recomienda no llamar a la base de datos ya que es lenta).
Estoy usando NUnit y Moq, pero no estoy seguro de cómo fingir esto.
Respondiendo a algunas de las respuestas que parecen estar todas basadas en ADO.NET, tenga en cuenta que estoy usando Linq para Sql. Entonces esas cosas son como detrás de escena.
Más información solicitada por @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()
Publicaciones en la primera línea cuando intenta maquetarse
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
.net
asp.net-mvc
moq
sqlexception
chobo2
fuente
fuente
Respuestas:
Dado que está utilizando Linq to Sql, aquí hay una muestra de prueba del escenario que mencionó utilizando NUnit y Moq. No conozco los detalles exactos de su DataContext y lo que tiene disponible en él. Edite según sus necesidades.
Deberá envolver el DataContext con una clase personalizada, no puede simular el DataContext con Moq. Tampoco puede burlarse de SqlException, porque está sellado. Deberá envolverlo con su propia clase de excepción. No es difícil lograr estas dos cosas.
Comencemos creando nuestra prueba:
[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); }
Implementemos la prueba, primero envolvemos nuestras llamadas de Linq a Sql usando el patrón de repositorio:
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); } }
A continuación, cree el IDataContextWrapper así, puede ver esta publicación de blog sobre el tema, la mía difiere un poco:
public interface IDataContextWrapper : IDisposable { Table<T> Table<T>() where T : class; }
A continuación, cree la clase CustomSqlException:
public class CustomSqlException : Exception { public CustomSqlException() { } public CustomSqlException(string message, SqlException innerException) : base(message, innerException) { } }
Aquí hay una implementación de muestra de 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 }
fuente
Puede hacer esto con reflexión, tendrá que mantenerlo cuando Microsoft haga cambios, pero funciona, lo acabo de probar:
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; } }
Esto también le permite controlar el número de SqlException, que puede ser importante.
fuente
NewSqlException
método para que lea:SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100, null);
Tengo una solución para esto. No estoy seguro de si es genialidad o locura.
El siguiente código creará una nueva 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); }
que luego puede usar así (este ejemplo está usando Moq)
mockSqlDataStore .Setup(x => x.ChangePassword(userId, It.IsAny<string>())) .Throws(MakeSqlException());
para que pueda probar su manejo de errores SqlException en sus repositorios, manejadores y controladores.
Ahora necesito acostarme.
fuente
new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1")
Dependiendo de la situación, normalmente prefiero GetUninitializedObject a invocar un ConstructorInfo. Solo debe tener en cuenta que no llama al constructor, de las observaciones de MSDN: "Debido a que la nueva instancia del objeto se inicializa a cero y no se ejecuta ningún constructor, es posible que el objeto no represente un estado que se considere válido por ese objeto ". Pero yo diría que es menos frágil que confiar en la existencia de cierto constructor.
[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; }
fuente
typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, "my custom sql message");
Editar Ouch: No me di cuenta de que SqlException está sellada. Me he estado burlando de DbException, que es una clase abstracta.
No puede crear una nueva SqlException, pero puede simular una DbException, de la que se deriva SqlException. Prueba esto:
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);
Entonces, su excepción se lanza cuando el método intenta abrir la conexión.
Si espera leer otra cosa que no sea la
Message
propiedad en la excepción simulada, no olvide Esperar (o Configurar, dependiendo de su versión de Moq) el "obtener" en esas propiedades.fuente
No estoy seguro de si esto ayuda, pero parece haber funcionado para esta persona (bastante inteligente).
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" }
Encontrado en: http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html
fuente
Esto debería funcionar:
SqlConnection bogusConn = new SqlConnection("Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;"); bogusConn.Open();
Eso toma un poco antes de que arroje la excepción, así que creo que esto funcionaría aún más rápido:
SqlCommand bogusCommand = new SqlCommand(); bogusCommand.ExecuteScalar();
Código presentado por Hacks-R-Us.
Actualización : no, el segundo enfoque arroja una ArgumentException, no una SqlException.
Actualización 2 : esto funciona mucho más rápido (la SqlException se lanza en menos de un segundo):
SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection Timeout=1"); bogusConn.Open();
fuente
Me di cuenta de que su pregunta tiene un año, pero para que conste, me gustaría agregar una solución que descubrí recientemente usando microsoft Moles (puede encontrar referencias aquí Microsoft Moles )
Una vez que haya modelado el espacio de nombres System.Data, simplemente puede simular una excepción SQL en un SqlConnection.Open () como este:
//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; };
Espero que esto pueda ayudar a alguien que tenga esta pregunta en el futuro.
fuente
Estas soluciones se sienten hinchadas.
El ctor es interno, sí.
(Sin usar la reflexión, la forma más fácil de crear realmente esta excepción ...
instance.Setup(x => x.MyMethod()) .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());
Perphaps hay otro método que no requiere el tiempo de espera de 1 segundo para lanzar.
fuente
(Sry es 6 meses tarde, espero que esto no se considere necroposting. Aterricé aquí buscando cómo lanzar una SqlCeException desde una simulación).
Si solo necesita probar el código que maneja la excepción, una solución alternativa ultra simple sería:
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());
fuente
Basado en todas las otras respuestas, creé la siguiente solución:
[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(); }
fuente
Esto es muy antiguo y aquí hay algunas buenas respuestas. Estoy usando Moq, y no puedo simular clases abstractas y realmente no quería usar la reflexión, así que hice mi propia excepción derivada de DbException. Entonces:
public class MockDbException : DbException { public MockDbException(string message) : base (message) {} }
obviamente, si necesita agregar InnerException, o lo que sea, agregue más accesorios, constructores, etc.
luego, en mi prueba:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
Con suerte, esto ayudará a cualquiera que esté usando Moq. Gracias a todos los que publicaron aquí que me llevaron a mi respuesta.
fuente
Sugiero usar este método.
/// <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() }); }
fuente
SqlException
no tiene un constructor yerrorConstructor
será nulo.Puede usar la reflexión para crear el objeto SqlException en la prueba:
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 });
fuente
private SqlException(string message, SqlErrorCollection errorCollection, Exception innerException, Guid conId)