116 Stimmen

Wie man eine SqlException auslösen, wenn für Mocking und Unit Testing benötigt?

Ich versuche, einige Ausnahmen in meinem Projekt zu testen und eine der Ausnahmen, die ich abfangen ist SQlException .

Es scheint, dass Sie nicht gehen können new SqlException() so bin ich nicht sicher, wie ich eine Ausnahme vor allem ohne irgendwie Aufruf der Datenbank auslösen kann (und da diese Unit-Tests sind es in der Regel empfohlen, nicht die Datenbank aufrufen, da es langsam ist).

Ich verwende NUnit und Moq, aber ich bin mir nicht sicher, wie man dies vortäuscht.

Als Antwort auf einige der Antworten, die alle auf ADO.NET zu basieren scheinen, beachten Sie, dass ich Linq to Sql verwende. Also ist das Zeug wie hinter den Kulissen.

Weitere Informationen auf Anfrage von @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()

Postet in der ersten Zeile, wenn es versucht, ein Mockup zu erstellen

 var ex = new Mock<System.Data.SqlClient.SqlException>();
 ex.SetupGet(e => e.Message).Returns("Exception message");

114voto

Dylan Beattie Punkte 51678

Ich habe eine Lösung für dieses Problem. Ich bin mir nicht sicher, ob sie genial oder verrückt ist.

Der folgende Code wird eine neue SqlException erstellen:

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

die Sie dann wie folgt verwenden können (in diesem Beispiel wird Moq verwendet)

mockSqlDataStore
    .Setup(x => x.ChangePassword(userId, It.IsAny<string>()))
    .Throws(MakeSqlException());

so dass Sie Ihre SqlException-Fehlerbehandlung in Ihren Repositories, Handlern und Controllern testen können.

Ich muss mich jetzt hinlegen.

102voto

Sam Saffron Punkte 124121

Sie können dies mit Reflexion tun, müssen Sie es zu pflegen, wenn Microsoft Änderungen vornehmen, aber es funktioniert ich habe es gerade getestet:

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

Dadurch können Sie auch die Anzahl der SqlException kontrollieren, was wichtig sein kann.

36voto

default.kramer Punkte 5795

Je nach Situation bevorzuge ich normalerweise GetUninitializedObject zum Aufrufen einer ConstructorInfo. Man muss sich nur bewusst sein, dass es den Konstruktor nicht aufruft - aus den MSDN Remarks: "Da die neue Instanz des Objekts auf Null initialisiert wird und keine Konstruktoren ausgeführt werden, repräsentiert das Objekt möglicherweise keinen Zustand, der von diesem Objekt als gültig angesehen wird." Aber ich würde sagen, es ist weniger brüchig als sich auf die Existenz eines bestimmten Konstruktors zu verlassen.

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

17voto

jjxtra Punkte 19065

Wenn Sie das neue Nuget Microsoft.Data.SqlClient verwenden, können Sie diese Hilfsmethode verwenden:

public static class SqlExceptionCreator
{
    public static SqlException Create(int number)
    {
        Exception? innerEx = null;
        var c = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        SqlErrorCollection errors = (c[0].Invoke(null) as SqlErrorCollection)!;
        var errorList = (errors.GetType().GetField("_errors", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(errors) as List<object>)!;
        c = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
        var nineC = c.FirstOrDefault(f => f.GetParameters().Length == 9)!;
        SqlError sqlError = (nineC.Invoke(new object?[] { number, (byte)0, (byte)0, "", "", "", (int)0, (uint)0, innerEx}) as SqlError)!;
        errorList.Add(sqlError);
        SqlException ex = (Activator.CreateInstance(typeof(SqlException), BindingFlags.NonPublic | BindingFlags.Instance, null, new object?[] { "test", errors,
            innerEx, Guid.NewGuid() }, null) as SqlException)!;
        return ex;
    }
}

13voto

Matt Hamilton Punkte 193704

Editar Autsch: Ich wusste nicht, dass SqlException versiegelt ist. Ich habe DbException verspottet, die eine abstrakte Klasse ist.

Sie können keine neue SqlException erstellen, aber Sie können eine DbException nachahmen, von der SqlException abgeleitet ist. Versuchen Sie dies:

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

Ihre Ausnahme wird also ausgelöst, wenn die Methode versucht, die Verbindung zu öffnen.

Wenn Sie erwarten, etwas anderes zu lesen als die Message Eigenschaft auf die gespottete Ausnahme dann vergessen Sie nicht, Expect (oder Setup, je nach Ihrer Version von Moq) die "get" auf diese Eigenschaften.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X