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

9voto

Dale Ragan Punkte 18091

Da Sie Linq to Sql verwenden, finden Sie hier ein Beispiel für das Testen des von Ihnen erwähnten Szenarios mit NUnit und Moq. Ich kenne nicht die genauen Details Ihres DataContext und was Sie darin zur Verfügung haben. Bearbeiten Sie es für Ihre Bedürfnisse.

Sie müssen den DataContext mit einer benutzerdefinierten Klasse umhüllen, Sie können den DataContext nicht mit Moq mocken. Sie können auch nicht SqlException nachbilden, da diese Klasse versiegelt ist. Sie müssen sie mit Ihrer eigenen Exception-Klasse umhüllen. Es ist nicht allzu schwierig, diese beiden Dinge zu erreichen.

Beginnen wir mit der Erstellung unseres Tests:

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

Implementieren wir den Test, verpacken wir zunächst unsere Linq to Sql-Aufrufe mit dem Repository-Muster:

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

Als Nächstes erstellen Sie den IDataContextWrapper wie folgt, Sie können dies sehen Blogbeitrag zu diesem Thema, meine unterscheidet sich ein wenig:

public interface IDataContextWrapper : IDisposable
{
    Table<T> Table<T>() where T : class;
}

Als nächstes erstellen Sie die Klasse CustomSqlException:

public class CustomSqlException : Exception
{
 public CustomSqlException()
 {
 }

 public CustomSqlException(string message, SqlException innerException) : base(message, innerException)
 {
 }
}

Hier ist eine Beispielimplementierung des 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
}

5voto

David Punkte 2483

Ich weiß nicht, ob das hilft, aber bei dieser Person scheint es funktioniert zu haben (ziemlich 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"
}

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

4voto

Diese Lösungen fühlen sich aufgebläht an.

Der Ctor ist intern, ja.

(Ohne die Verwendung von Reflexion, der einfachste Weg, um nur wirklich diese Ausnahme zu erstellen....

   instance.Setup(x => x.MyMethod())
            .Callback(() => new SqlConnection("Server=pleasethrow;Database=anexception;Connection Timeout=1").Open());

Vielleicht gibt es eine andere Methode, bei der die Zeitüberschreitung von 1 Sekunde nicht erforderlich ist.

3voto

Luiz Lanza Punkte 144

Ich schlage vor, diese Methode anzuwenden.

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

2voto

MusiGenesis Punkte 72729

Das sollte funktionieren:

SqlConnection bogusConn = 
    new SqlConnection("Data Source=myServerAddress;Initial
    Catalog=myDataBase;User Id=myUsername;Password=myPassword;");
bogusConn.Open();

Es dauert ein bisschen, bis die Ausnahme ausgelöst wird, daher denke ich, dass dies noch schneller funktionieren würde:

SqlCommand bogusCommand = new SqlCommand();
bogusCommand.ExecuteScalar();

Der Code wurde Ihnen von Hacks-R-Us zur Verfügung gestellt.

Update Nein, der zweite Ansatz wirft eine ArgumentException, nicht eine SqlException.

Aktualisierung 2 : Dies funktioniert viel schneller (die SqlException wird in weniger als einer Sekunde ausgelöst):

SqlConnection bogusConn = new SqlConnection("Data Source=localhost;Initial
    Catalog=myDataBase;User Id=myUsername;Password=myPassword;Connection
    Timeout=1");
bogusConn.Open();

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