38

特定の名前がメソッドに渡されると、SQL 例外がスローされることをテストする次のコードがあります (少し奇妙に聞こえますが、これには理由があります)。

   mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), 
"Display Name 2", It.IsAny<string>())).Throws<SqlException>();

ただし、SqlException のコンストラクターが内部であるため、これはコンパイルされません。

'System.Data.SqlClient.SqlException' は、ジェネリック型またはメソッド 'Moq.Language.IThrows.Throws()' でパラメーター 'TException' として使用するために、パブリック パラメーターなしのコンストラクターを持つ非抽象型である必要があります。

ここで、これを をスローするように変更できますが、メソッドが の場合は 1 つのステータス コードを返し、他の例外の場合は別Exceptionのステータス コードを返す必要があるため、それはうまくいきません。SqlExceptionそれが私の単体テストがテストしているものです。

テストしているメソッドのロジックを変更せずに、またはこのシナリオをテストせずにこれを達成する方法はありますか?

4

5 に答える 5

64

Number例外のまたはプロパティのテスト ケースが必要な場合はMessage、次のようなビルダー (リフレクションを使用) を使用できます。

using System;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;

public class SqlExceptionBuilder
{
    private int errorNumber;
    private string errorMessage;

    public SqlException Build()
    {
        SqlError error = this.CreateError();
        SqlErrorCollection errorCollection = this.CreateErrorCollection(error);
        SqlException exception = this.CreateException(errorCollection);

        return exception;
    }

    public SqlExceptionBuilder WithErrorNumber(int number)
    {
        this.errorNumber = number;
        return this;
    }

    public SqlExceptionBuilder WithErrorMessage(string message)
    {
        this.errorMessage = message;
        return this;
    }

    private SqlError CreateError()
    {
        // Create instance via reflection...
        var ctors = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        var firstSqlErrorCtor = ctors.FirstOrDefault(
            ctor =>
            ctor.GetParameters().Count() == 7); // Need a specific constructor!
        SqlError error = firstSqlErrorCtor.Invoke(
            new object[] 
            { 
                this.errorNumber, 
                new byte(), 
                new byte(), 
                string.Empty, 
                string.Empty, 
                string.Empty, 
                new int() 
            }) as SqlError;

        return error;
    }
 
    private SqlErrorCollection CreateErrorCollection(SqlError error)
    {
        // Create instance via reflection...
        var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        SqlErrorCollection errorCollection = sqlErrorCollectionCtor.Invoke(new object[] { }) as SqlErrorCollection;

        // Add error...
        typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(errorCollection, new object[] { error });

        return errorCollection;
    }

    private SqlException CreateException(SqlErrorCollection errorCollection)
    {
        // Create instance via reflection...
        var ctor = typeof(SqlException).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        SqlException sqlException = ctor.Invoke(
            new object[] 
            { 
                // With message and error collection...
                this.errorMessage, 
                errorCollection,
                null,
                Guid.NewGuid() 
            }) as SqlException;

        return sqlException;
    }
}

次に、(たとえば) リポジトリ モックに次のような例外をスローさせることができます (この例ではMoqライブラリを使用しています)。

using Moq;

var sqlException = 
    new SqlExceptionBuilder().WithErrorNumber(50000)
        .WithErrorMessage("Database exception occured...")
        .Build();
var repoStub = new Mock<IRepository<Product>>(); // Or whatever...
repoStub.Setup(stub => stub.GetById(1))
    .Throws(sqlException);
于 2015-04-29T09:31:48.467 に答える
55

これはうまくいくはずです:

using System.Runtime.Serialization;

var exception = FormatterServices.GetUninitializedObject(typeof(SqlException)) 
                as SqlException;

mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), "Display Name 2", 
                     It.IsAny<string>())).Throws(exception);

ただし、使用GetUninitializedObjectには次の注意事項があります。

オブジェクトの新しいインスタンスはゼロに初期化され、コンストラクターは実行されないため、オブジェクトは、そのオブジェクトによって有効と見なされる状態を表していない可能性があります。

これにより問題が発生する場合は、おそらくもっと複雑なリフレクション マジックを使用して作成できますが、この方法がおそらく最も簡単です (機能する場合)。

于 2012-08-15T21:11:12.557 に答える
7

これを試してみたところ、うまくいきました:

private static void ThrowSqlException()
{
    using (var cxn = new SqlConnection("Connection Timeout=1"))
    {
        cxn.Open();
    }
}

// ...
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>),
                     "Display Name 2", It.IsAny<string>()))
              .Callback(() => ThrowSqlException());
于 2012-08-15T21:16:19.623 に答える
1

メッセージ付きのを作成するSqlExceptionには、 Uninitialized Object メソッドを使用するのが最も簡単な方法でした。

const string sqlErrorMessage = "MyCustomMessage";
var sqlException = FormatterServices.GetUninitializedObject(typeof(SqlException)) as SqlException;
var messageField = typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance);
messageField.SetValue(sqlException, sqlErrorMessage);
于 2019-01-28T13:56:54.673 に答える