Как бросить SqlException, когда это необходимо для издевательства и модульного тестирования?

Я пытаюсь проверить некоторые исключения в моем проекте, и одним из исключений, которые я ловлю, является 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");

13 ответов


поскольку вы используете 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 to 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, которое может быть важным.


у меня есть решение для этого. Не знаю, гениально это или безумно.

следующий код создаст новое исключение 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 в своих репозиториях, обработчиках и контроллерах.

теперь мне нужно пойти и лечь.


в зависимости от ситуации, я обычно предпочитаю 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;
}

редактировать Ой: я не понял, что 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) "get" на этих свойствах.


Не уверен, если это помогает, Но, похоже, работал для этого человека (довольно умный).

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 Кроты)

после того, как вы неся moled системы.Пространство имен данных, вы можете просто подделать исключение 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;
            };

Я надеюсь, что это может помочь кому-то, что делает этот вопрос в будущем.


(Sry это 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));

Hoepfully это поможет любому, кто использует 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 });

Я предлагаю использовать этот метод.

    /// <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() });
    }