2012-08-15 11 views
22

Mam następujący kod do sprawdzenia, że ​​po przekazaniu pewnej nazwy do mojej metody, rzuca wyjątek SQL (nie ma powodu do tego, choć brzmi to trochę dziwne).Moq i rzucając wyjątek SqlException

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

Jednak nie będzie to skompilować, ponieważ konstruktor SQLException jest wewnętrzny:

„System.Data.SqlClient.SqlException” musi być non-abstrakcyjny typ z konstruktora bez parametrów publicznego w celu używać go jako parametr „TException” w ogólnym typie lub metodzie „Moq.Language.IThrows.Throws()”

teraz mogę to zmienić, aby stwierdzić, że powinien rzucić Exception, ale to by nie działało dla mnie, ponieważ moja metoda powinna zwrócić jeden kod stanu, jeśli jest to SqlException, a drugi, jeśli jest jakikolwiek inny wyjątek. Właśnie to testuje mój test jednostkowy.

Czy jest jakiś sposób, aby to osiągnąć, nie zmieniając logiki metody, którą testuję, lub nie testując tego scenariusza?

+1

możliwy duplikat [Jak rzucić wyjątek SqlException (potrzeba kpiny)] (http://stackoverflow.com/questions/1386962/how-to-throw-a-sqlexceptionneed- for-mocking) –

+1

Możesz użyć Odbicie do dostęp do metody wewnętrznej CreateException, która pozwala utworzyć wyjątek SQLException. http://stackoverflow.com/questions/1259222/how-to-access-internal-class-using-reflection .... http://msdn.microsoft.com/en-us/library/ms229394(v=vs .100) .aspx ... po prostu zrób lamdę, aby ją utworzyć i wyrzucić. –

Odpowiedz

33

To powinno działać:

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

Jednak użycie GetUninitializedObject ma to zastrzeżenie:

Ponieważ nowa instancja obiektu jest inicjowany do zera i nie ma konstruktorzy są uruchamiane, obiekt może nie reprezentuje stanu, który jest uznawany za ważny przez ten obiekt.

Jeśli to powoduje problemy, prawdopodobnie można go utworzyć za pomocą bardziej zaangażowanej magii odbicia, ale ta metoda jest prawdopodobnie najprostsza (jeśli działa).

+0

Nie byłoby sposobu na ustawienie wyjątku, prawda? – bump

+0

@bump Może to być możliwe poprzez odbicie, ale w zależności od struktury podrzędnej może być dość trudne (np. Uzyskanie pól właściwości i ich ustawienie). Nie jestem pewien, jakie ustawienie daje ci wiadomość, chyba że masz inną logikę działającą w oparciu o to, co stwierdza komunikat wyjątku i musisz to przetestować. – docmanhattan

+0

Naprawdę potrzebowałem przetestować to.Udało się to zrobić za pomocą refleksji (i prywatnego konstruktora). Dzięki. – bump

5

Właśnie próbowałem, i pracował dla mnie:

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()); 
+0

Co się stanie, jeśli 'CreateAccount' zwróci puste? –

+1

Prawdopodobnie w każdym razie będziesz chciał użyć '.Callback', teraz, kiedy o tym myślę. Aktualizuję odpowiedź. –

30

Jeśli potrzebujesz przypadków testowych dla Number lub Message właściwości wyjątku, można użyć konstruktora (który używa refleksji) tak :

using System; 
using System.Data.SqlClient; 
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; 
    } 
} 

Wtedy można mieć repozytorium makiety (na przykład) wyjątek takiego:

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

Dziękuję za to !! : D –

+0

@StephanRyer Miałem kod ... po prostu myślałem, że podzielę się – bump

+1

Jesteś dżentelmenem i uczonym. Musisz to zrobić na Githubie, żeby móc go zagrać. –

Powiązane problemy