Estou tentando testar algumas exceções em meu projeto e uma das exceções que pego é SQlException
.
Parece que você não pode ir, new SqlException()
então não tenho certeza de como posso lançar uma exceção, especialmente sem chamar o banco de dados de alguma forma (e como esses são testes de unidade, geralmente é aconselhável não chamar o banco de dados, pois ele é lento).
Estou usando o NUnit e o Moq, mas não tenho certeza de como fingir.
Respondendo a algumas das respostas que parecem ser baseadas no ADO.NET, observe que estou usando o Linq to Sql. Então, essas coisas são como nos bastidores.
Mais informações solicitadas 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()
Publica na primeira linha quando tenta fazer a maquete
var ex = new Mock<System.Data.SqlClient.SqlException>();
ex.SetupGet(e => e.Message).Returns("Exception message");
.net
asp.net-mvc
moq
sqlexception
chobo2
fonte
fonte
Respostas:
Como você está usando o Linq to Sql, aqui está um exemplo de teste do cenário que você mencionou usando o NUnit e o Moq. Não sei os detalhes exatos do seu DataContext e o que você tem disponível nele. Edite de acordo com suas necessidades.
Você precisará envolver o DataContext com uma classe personalizada, você não pode simular o DataContext com Moq. Você também não pode simular SqlException, porque está lacrado. Você precisará envolvê-lo com sua própria classe de exceção. Não é muito difícil realizar essas duas coisas.
Vamos começar criando nosso teste:
[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); }
Vamos implementar o teste, primeiro vamos envolver nossas chamadas Linq para Sql usando o padrão de repositório:
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); } }
Em seguida, crie o IDataContextWrapper assim, você pode ver esta postagem do blog sobre o assunto, a minha é um pouco diferente:
public interface IDataContextWrapper : IDisposable { Table<T> Table<T>() where T : class; }
Em seguida, crie a classe CustomSqlException:
public class CustomSqlException : Exception { public CustomSqlException() { } public CustomSqlException(string message, SqlException innerException) : base(message, innerException) { } }
Aqui está um exemplo de implementação do 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 }
fonte
Você pode fazer isso com reflexão, você terá que mantê-lo quando a Microsoft fizer alterações, mas funciona, acabei de testar:
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; } }
Isso também permite que você controle o número da SqlException, que pode ser importante.
fonte
NewSqlException
método para ler:SqlError error = Construct<SqlError>(number, (byte)2, (byte)3, "server name", "error message", "proc", 100, null);
Eu tenho uma solução para isso. Não tenho certeza se é gênio ou loucura.
O código a seguir criará uma nova 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 você pode usar como tal (este exemplo está usando Moq)
mockSqlDataStore .Setup(x => x.ChangePassword(userId, It.IsAny<string>())) .Throws(MakeSqlException());
para que você possa testar o tratamento de erros SqlException em seus repositórios, manipuladores e controladores.
Agora preciso ir me deitar.
fonte
new SqlConnection(@"Data Source=.;Database=GUARANTEED_TO_FAIL;Connection Timeout=1")
Dependendo da situação, geralmente prefiro GetUninitializedObject a invocar um ConstructorInfo. Você apenas precisa estar ciente de que ele não chama o construtor - dos comentários do MSDN: "Como a nova instância do objeto é inicializada com zero e nenhum construtor é executado, o objeto pode não representar um estado considerado válido por esse objeto. " Mas eu diria que é menos frágil do que confiar na existência de um certo construtor.
[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; }
fonte
typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, "my custom sql message");
Editar Ouch: Eu não percebi que SqlException está selado. Tenho zombado de DbException, que é uma classe abstrata.
Você não pode criar uma nova SqlException, mas pode simular uma DbException, da qual SqlException deriva. Experimente isto:
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);
Portanto, sua exceção é lançada quando o método tenta abrir a conexão.
Se você espera ler qualquer coisa diferente da
Message
propriedade na exceção simulada, não se esqueça de esperar (ou configurar, dependendo da sua versão do Moq) o "get" nessas propriedades.fonte
Não tenho certeza se isso ajuda, mas parece ter funcionado para essa pessoa (muito 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 em: http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html
fonte
Isso deve funcionar:
SqlConnection bogusConn = new SqlConnection("Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;"); bogusConn.Open();
Isso leva um pouco antes de lançar a exceção, então acho que funcionaria ainda mais rápido:
SqlCommand bogusCommand = new SqlCommand(); bogusCommand.ExecuteScalar();
Código trazido a você pela Hacks-R-Us.
Atualizar : não, a segunda abordagem lança uma ArgumentException, não uma SqlException.
Atualização 2 : isso funciona muito mais rápido (a SqlException é lançada em menos de um segundo):
SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection Timeout=1"); bogusConn.Open();
fonte
Percebi que sua pergunta tem um ano de idade, mas para registro, gostaria de adicionar uma solução que descobri recentemente usando os Moles da Microsoft (você pode encontrar referências aqui Moles da Microsoft )
Depois de modificar o namespace System.Data, você pode simplesmente simular uma exceção SQL em um 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 isso possa ajudar alguém que tenha essa pergunta no futuro.
fonte
Eu sugiro 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() }); }
fonte
SqlException
não tem um construtor eerrorConstructor
será nulo.Essas soluções parecem inchadas.
O ctor é interno, sim.
(Sem usar reflexão, a maneira mais fácil de criar genuinamente essa exceção ....
instance.Setup(x => x.MyMethod()) .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());
Talvez haja outro método que não exija o tempo limite de 1 segundo para ser lançado.
fonte
(Sry, está 6 meses atrasado, espero que isso não seja considerado necroposting. Vim aqui procurando como lançar um SqlCeException de um mock).
Se você só precisa testar o código que lida com a exceção, uma solução ultra simples seria:
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());
fonte
Com base em todas as outras respostas, criei a seguinte solução:
[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(); }
fonte
Isso é muito antigo e existem algumas boas respostas aqui. Estou usando Moq e não posso simular classes abstratas e realmente não queria usar reflexão, então fiz minha própria exceção derivada de DbException. Então:
public class MockDbException : DbException { public MockDbException(string message) : base (message) {} }
obviamente, se você precisar adicionar InnerException, ou qualquer outra coisa, adicione mais adereços, construtores, etc.
então, em meu teste:
MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
Felizmente, isso ajudará qualquer pessoa que esteja usando o Moq. Obrigado a todos que postaram aqui que me levaram à minha resposta.
fonte
Você pode usar reflexão para criar o objeto SqlException no teste:
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 });
fonte
private SqlException(string message, SqlErrorCollection errorCollection, Exception innerException, Guid conId)