2013-01-10 5 views
8

Używam Moq do sprawdzenia, czy metoda jest wywoływana w moim unittest. W tym konkretnym przypadku chcę przetestować, czy testowana metoda rejestruje błąd przez log4net. Problem polega na tym, że można to zrobić, dzwoniąc pod numer log.Error lub log.ErrorFormat. Albo jest w porządku.Jak sprawdzić, czy została wywołana jedna z dwóch metod?

Jak mogę to jednak zweryfikować? Wiem tylko, jak sprawdzić, czy obaj zostali nazwani.

var logMock = new Mock<ILog>(); 

var myClass = new MyClass(logMock.Object); 

myClass.MyMethod(); 

logMock.Verify(log => log.Error(It.IsAny<object>())); 
logMock.Verify(log => log.ErrorFormat(It.IsAny<string>(), It.IsAny<object>())); 

Teraz, gdy myślę o tym, że obie mają kilka przeciążeń, nie mam nic przeciwko temu, jeśli któryś z przeciążeniami nazywane są albo (Zaczynam wątpić, że jest to dobry test).

Z góry dziękuję.

EDIT: Właśnie myślałem o czymś złośliwym:

try 
{ 
    logMock.Verify(log => log.Error(It.IsAny<object>())); 
} 
catch (Moq.MockException ex) 
{ 
    logMock.Verify(log => log.ErrorFormat(It.IsAny<string>(), It.IsAny<object>())); 
} 

Może mogę owinąć to w jakiś sposób ... np wydłużania VerifyAny.

Odpowiedz

9

Można zarejestrować wywołania zwrotnego dla każdej ważnej metody błędu, który ustawia flagę:

// Arrange 
bool errorFlag = false; 
logMock 
    .Setup(l => l.Error(It.IsAny<object>())) 
    .Callback((object o) => errorFlag = true); 

/* repeat setup for each logMock method */ 

// Act 
myClass.MyMethod(); 

// Assert 
Assert.IsTrue(errorFlag); 

Oczywiście, to wciąż będzie uciążliwe, jeśli masz wiele przeciążeń na okładce.

EDIT: A dla zabawy, tutaj jest metoda rozszerzenie dla Mock<T>.VerifyAny:

public static class MockExtensions 
{ 
    public static void VerifyAny<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) 
     where T: class 
    { 
     List<MockException> exceptions = new List<MockException>(); 
     bool success = false; 
     foreach (var expression in expressions) 
     { 
      try 
      { 
       mock.Verify(expression); 
       success = true; 
       break; 
      } 
      catch (MockException ex) 
      { 
       exceptions.Add(ex); 
      } 
     } 

     if (!success) 
     { 
      throw new AggregateException("None of the specified methods were invoked.", exceptions); 
     } 
    } 
} 

Zastosowanie:

[TestMethod] 
public void FooTest() 
{ 
    Mock<IFoo> fooMock = new Mock<IFoo>(); 
    fooMock.Object.Bar1(); 

    fooMock.VerifyAny(
     f => f.Bar1(), 
     f => f.Bar2()); 
} 
+0

Moja wersja VerifyAny była prawie taka sama, z wyjątkiem, że nie pomyśl o ograniczeniu typu i zbieraniu wyjątków. Używanie jest w rzeczywistości nieco czystsze niż podejście CallBack. A może złapanie wyjątków to zły pomysł? Hmmm .... –

+0

@MatthijsWessels Łapanie oczekiwanych wyjątków, takich jak to, wydaje się trochę brzydkie, ale dla projektu testów jednostkowych nie przejmowałbym się zbytnio występem. Być może byłoby lepiej mieć zamiast tego metodę "Mock .SetupAny", która wstrzykuje wywołania zwrotne i zwraca wartość logiczną. –

1

jeśli są specjalnie testowanie że konkretny błąd został zalogowany, dlaczego nie ma 2 testy, jeden który zapewnia, że ​​log.Error jest wywoływany i taki, który zapewnia, że ​​log.ErrorFormat jest wywoływany, zakładam, że możesz kontrolować, który z nich jest wywoływany na podstawie danych wejściowych.

jeśli nadal chce zweryfikować jedną lub drugą stronę, można po prostu użyć tej metody, robi dokładnie to, czego potrzebujesz:

Verify that either one method or the other was invoked in a unit test

+1

Wymaganiem dla 'MyClass' jest to, że loguje komunikat dziennika poziomu błędu. Błąd, ErrorFormat i ich przeciążenia to wszystko. Nie ma dla mnie znaczenia, który z nich jest używany. Metoda CallBack określona w odpowiedzi, którą połączyłeś, wygląda na to, że może zrobić to, co chcę. Chociaż teraz wątpię w VerifyAll lub CallBack firmy Scott Wegner. –