How to throw a SqlException when needed for mocking and unit testing?

前端 未结 14 1678
孤街浪徒
孤街浪徒 2020-12-05 01:28

I am trying to test some exceptions in my project and one of the Exceptions I catch is SQlException.

It seems that you can\'t go new SqlException(

相关标签:
14条回答
  • 2020-12-05 02:09

    You can do this with reflection, you will have to maintain it when Microsoft make changes, but it does work I just tested it:

    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;
        }
    }      
    

    This also allows you to control the Number of the SqlException, which can be important.

    0 讨论(0)
  • 2020-12-05 02:09

    Edit Ouch: I didn't realise SqlException is sealed. I've been mocking DbException, which is an abstract class.

    You can't create a new SqlException, but you can mock a DbException, which SqlException derives from. Try this:

    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);
    

    So your exception is thrown when the method tries to open the connection.

    If you expect to read anything other than the Message property on the mocked exception then don't forget to Expect (or Setup, depending on your version of Moq) the "get" on those properties.

    0 讨论(0)
  • 2020-12-05 02:09

    Not sure if this helps, but seems to have worked for this person (pretty clever).

    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"
    }
    

    Found at: http://smartypeeps.blogspot.com/2006/06/how-to-throw-sqlexception-in-c.html

    0 讨论(0)
  • 2020-12-05 02:11

    I suggest using this method.

        /// <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() });
        }
    
    0 讨论(0)
  • 2020-12-05 02:13

    I noticed that your question is one year old, but for the record I would like to add a solution I discovered recently using microsoft Moles (you can find references here Microsoft Moles)

    Once you haved moled the System.Data namespace, you can simply mock an SQL exception on a SqlConnection.Open() like this :

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

    I hope this can help someone that hits this question in the future.

    0 讨论(0)
  • 2020-12-05 02:14

    This is really old and there are some good answers here. I am using Moq, and I can't mock up Abstract classes and really didn't want to use reflection, so I made my own Exception derived from DbException. So:

    public class MockDbException : DbException {
      public MockDbException(string message) : base (message) {}
    }   
    

    obviously, if you need to add InnerException, or whatever, add more props, constructors, etc.

    then, in my test:

    MyMockDatabase.Setup(q => q.Method()).Throws(new MockDbException(myMessage));
    

    Hoepfully this will help anyone that's using Moq. Thanks for everyone that posted in here that led me to my answer.

    0 讨论(0)
提交回复
热议问题