.net - una - meta etiquetas
¿Cómo lanzar una SqlException cuando sea necesario para burlarse y probar la unidad? (13)
(Sry es 6 meses tarde, espero que esto no se considere necroposting. Llegué aquí buscando cómo lanzar una SqlCeException desde un simulacro).
Si solo necesita probar el código que maneja la excepción, una solución 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());
Estoy intentando probar algunas excepciones en mi proyecto y una de las Excepciones que capturo es SQlException
.
Parece que no puedes ir a la 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 falsificar esto.
Respondiendo a algunas de las respuestas que parecen basarse en ADO.NET, tenga en cuenta que estoy utilizando 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 maqueta
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
Dado que está utilizando Linq a Sql, aquí hay una muestra de prueba del escenario que mencionó usando NUnit y Moq. No sé los detalles exactos de su DataContext y lo que tiene disponible en él. Edita para tus necesidades.
Tendrá que ajustar el DataContext con una clase personalizada, no puede simular el DataContext con Moq. Tampoco puedes burlarte de SqlException porque está sellado. Tendrá que 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);
}
}
Luego crea el IDataContextWrapper así, puedes ver esta publicación del blog sobre el tema, la mía difiere un poco:
public interface IDataContextWrapper : IDisposable
{
Table<T> Table<T>() where T : class;
}
Luego crea la clase CustomSqlException:
public class CustomSqlException : Exception
{
public CustomSqlException()
{
}
public CustomSqlException(string message, SqlException innerException) : base(message, innerException)
{
}
}
Aquí hay una implementación de ejemplo 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
}
Dependiendo de la situación, generalmente prefiero GetUninitializedObject para invocar a ConstructorInfo. Solo debe tener en cuenta que no llama al constructor - desde MSDN Observaciones: "Debido a que la nueva instancia del objeto se inicializa a cero y no se ejecutan constructores, el objeto puede no representar un estado que se considera válido por ese objeto ". Pero diría que es menos frágil que confiar en la existencia de un determinado 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;
}
En base a todas las demás 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();
}
Esto debería funcionar:
SqlConnection bogusConn =
new SqlConnection("Data Source=myServerAddress;Initial
Catalog=myDataBase;User Id=myUsername;Password=myPassword;");
bogusConn.Open();
Eso demora 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();
Esto es muy viejo y hay algunas buenas respuestas aquí. 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. Asi que:
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.
entonces, en mi prueba:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
A toda prisa esto ayudará a cualquiera que esté usando Moq. Gracias a todos los que publicaron aquí que me llevaron a mi respuesta.
Me di cuenta de que su pregunta tiene un año de antigüedad, pero para que quede constancia, me gustaría agregar una solución que descubrí recientemente usando Microsoft Moles (puede encontrar referencias aquí, Microsoft Moles )
Una vez que haya emparejado 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 llegue a esta pregunta en el futuro.
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
Puede usar el reflejo 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 });
Puedes hacer esto con reflexión, tendrás que mantenerlo cuando Microsoft realice cambios, pero funciona. Acabo de probarlo:
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.
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() });
}
Tengo una solución para esto. No estoy seguro si es genio 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í (en este ejemplo se usa Moq)
mockSqlDataStore
.Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
.Throws(MakeSqlException());
para que pueda probar el manejo de errores SqlException en sus repositorios, controladores y controladores.
Ahora necesito ir y acostarme.
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 cual proviene 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, se lanza su excepción cuando el método intenta abrir la conexión.
Si espera leer otra cosa que no sea la propiedad Message
en la excepción simulada, entonces no se olvide de esperar (o Configurar, dependiendo de su versión de Moq) el "obtener" en esas propiedades.