2010-05-12 16 views
30

Naprawdę doceniam zachowanie kpiące z Moq, które zwraca wartości domyślne, gdy nie są ustawione żadne oczekiwania. Jest to wygodne i zapisuje kod, a także działa jako środek bezpieczeństwa: zależności nie zostaną przypadkowo wywołane podczas testu urządzenia (o ile są wirtualne).Podczas kpiny z klasy z Moq, jak mogę CallBase dla tylko konkretnych metod?

Jestem jednak zdezorientowany, jak zachować te korzyści, gdy testowana metoda stanie się wirtualna.
W tym przypadku I do chce wywoływać prawdziwy kod dla tej jednej metody, a jeszcze luźno kpią z reszty klasy.

Wszystko, co znalazłem w moich poszukiwaniach, to że mogę ustawić mock.CallBase = true, aby zapewnić wywołanie metody. Wpływa to jednak na całą klasę. Nie chcę tego robić, bo to stawia mnie w dylemat na temat wszystkich innych właściwości i metod klasy, które ukrywają zależności połączeń: jeśli CallBase prawda to mam albo

  1. odcinki instalacyjnych dla wszystkich właściwości i metody, które ukrywają zależności - mimo że mój test nie wymaga dbania o te zależności, lub
  2. Mam nadzieję, że nie zapomnę skonfigurować żadnych skrótów (i że żadne nowe zależności nie zostaną dodane do kod w przyszłości) - Testy jednostek ryzyka uderzają w prawdziwą zależność.

Co myślę, że chcę coś jak:
mock.Setup(m => m.VirtualMethod()).CallBase();
tak, że gdy zgłoszę mock.Object.VirtualMethod(), Moq wzywa do rzeczywistej realizacji ...

Q: Z Min, czy jest jakiś sposób przetestować wirtualną metodę, gdy wyśmiewam się z klasy, żeby wyprowadzić kilka zależności? To znaczy. Bez odwoływania się do CallBase = true i konieczności stub wszystkich zależności?


przykład kod ilustrują
(używa MSTest, InternalsVisibleTo DynamicProxyGenAssembly2)

W poniższym przykładzie TestNonVirtualMethod przejściach, ale nie TestVirtualMethod - zwraca puste.

public class Foo 
{ 
    public string NonVirtualMethod() { return GetDependencyA(); } 
    public virtual string VirtualMethod() { return GetDependencyA();} 

    internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; } 
    // [... Possibly many other dependencies ...] 
    internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; } 
} 

[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void TestNonVirtualMethod() 
    { 
     var mockFoo = new Mock<Foo>(); 
     mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); 

     string result = mockFoo.Object.NonVirtualMethod(); 

     Assert.AreEqual(expectedResultString, result); 
    } 

    [TestMethod] 
    public void TestVirtualMethod() // Fails 
    { 
     var mockFoo = new Mock<Foo>(); 
     mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); 
     // (I don't want to setup GetDependencyB ... GetDependencyN here) 

     string result = mockFoo.Object.VirtualMethod(); 

     Assert.AreEqual(expectedResultString, result); 
    } 

    string expectedResultString = "Hit mock dependency A - OK"; 
} 
+0

Wystarczy krótka z jakiego katalogu/moje potrzeby jest o: Need 'wersję CallBase' gdy szydząc, że zamiast * * "Wywołaj implementację klasy bazowej, jeśli żadne oczekiwania nie będą nadpisywać elementu" **, tak zwane _Partial Mocks_, po prostu wywołaj podstawową implementację wyśmianych członków! Chciałbym nazwać nową funkcję jako 'CallBaseOnMockedMemberOnly': D –

Odpowiedz

6

Uważam, że odpowiedź Lunivore była poprawna w momencie, gdy została napisana.

W nowszych wersjach Moq (myślę, że od wersji 4.1 od 2013 r.) Możliwe jest robienie tego, co chcesz, za pomocą dokładnie takiej składni, jaką proponujesz. Czyli:

mock.Setup(m => m.VirtualMethod()).CallBase(); 

To ustawia luźne makiety zadzwonić realizację podstawy VirtualMethod zamiast po prostu powrocie default(WhatEver), ale dla tego użytkownika (VirtualMethod) tylko.


Jak zauważa BornToCode łatwy w komentarzach, to nie będzie działać, jeśli metoda ma typ zwracany void. Gdy VirtualMethod jest nieważne, wywołanie daje Moq.Language.Flow.ISetup<TMock, TResult>, która dziedziczy metodę CallBase() od Moq.Language.Flow.IReturns<TMock, TResult>. Ale gdy metoda jest nieważna, zamiast tego dostajemy Moq.Language.Flow.ISetup<TMock>, która nie ma pożądanej metody CallBase().

+0

Nie robię już C# w mojej codziennej pracy, więc nie jestem w najlepszej pozycji do zweryfikowania, ale z chęcią zaktualizuję zaakceptowaną odpowiedź, by odzwierciedlić obecną rzeczywistość. Jeśli ktokolwiek może zadzwonić, aby potwierdzić nową odpowiedź @ Jeppe, zrób to! - Dzięki – Daryn

+0

@Daryn: Potwierdzam, że to działa, po prostu wypróbowałem. Czytanie tego pytania skończyło się przypadkowo, pomagając mi zrozumieć niepowiązany problem (dlaczego wszystkie moje metody nie są wirtualne nazywające ich podstawową implementację?), Więc pomyślałem, że zwrócę przysługę i spróbuję tego :) –

+0

Dziękuję, @CraigBrett. –

8

Ponieważ nikt nie odpowiedział na to pytanie do wieku i myślę, że zasługuje na odpowiedź, mam zamiar skupić się na pytaniu najwyższym poziomie ty zapytał: jak zachować te korzyści, gdy metoda badanego dzieje się wirtualny.

Szybka odpowiedź: nie można tego zrobić z Moq, a przynajmniej nie bezpośrednio. Ale możesz to zrobić.

Załóżmy, że masz dwa aspekty zachowania, w których aspekt A jest wirtualny, a aspekt B nie. To prawie odzwierciedla to, co masz w swojej klasie. B może używać innych metod lub nie; to zależy od Ciebie.

W tej chwili twoja klasa Foo robi dwie rzeczy - zarówno A, jak i B. Mogę powiedzieć, że są oddzielnymi obowiązkami tylko dlatego, że chcesz wyśmiewać A i przetestować B na własną rękę.

Zamiast starać się wyśmiewać się metodę wirtualną bez wyśmianie się niczego innego, można:

  • ruch zachowania się w oddzielnej klasie
  • zależność wprowadzić nową klasę z do Foo przez Foo ' s konstruktor
  • Wywołanie tej klasy z B.

teraz można drwić a, i jeszcze wywołać prawdziwy kod B..N wi tak naprawdę wywołując rzeczywisty A. Możesz zachować wirtualny lub uzyskać do niego dostęp za pośrednictwem interfejsu i kpić z niego. Jest to również zgodne z zasadą odpowiedzialności pojedynczej.

Można kaskadować konstruktorów za pomocą Foo - wywołanie konstruktora Foo() wywołanie konstruktora Foo(new A()) - więc nie trzeba do tego celu struktury wstrzyknięć zależności.

Mam nadzieję, że to pomoże!

+0

Wystarczy, aby sprawdzić: potwierdzasz, że jeśli testowana metoda jest wirtualna i używamy makiety , to czy musimy ustawić CallBase na true? – Daryn

+0

Sugeruję, że jeśli dzwonisz do CallBase, prawdopodobnie istnieje inny sposób uzyskania tej samej korzyści, co prawdopodobnie prowadzi do zaprojektowania kodu, który działa w większości kontekstów. Być może masz jeden z tych rzadkich kontekstów, który bezwzględnie wymaga CallBase - nie wiem. Czy możesz mi powiedzieć, dlaczego CallBase dla metody jest najprostszym rozwiązaniem twojego problemu? – Lunivore

+0

Nie jest to OP, ale myślę, że to pytanie jest nadal aktualne: CallBase jest najprostszym rozwiązaniem, gdy masz dość atomową logikę biznesową, która jest niezwykle kłopotliwa w testowaniu za pośrednictwem pojedynczego punktu wejścia. Dzielenie na klasy to dobra zasada, ale czasem może być zbyt kosztowna, ze starszym kodem lub przynajmniej niebezpiecznym punktem początkowym. Nadmierne rozszczepianie prowadzi również do biegunki typu, co powoduje, że rozwiązanie jest trudniejsze do odczytania (na przykład siatka 10 współzależnych klas, w których istnieje tylko jeden rozsądny punkt wejścia). –

-1

Nie jest sposób, aby wywołać prawdziwą metodę i mieć połączenie z powrotem, gdy metoda jest void, ale to naprawdę hacky. Musisz zadzwonić, aby zadzwonić do niego jawnie i oszukać Moqa, nazywając prawdziwego.

Na przykład, biorąc pod uwagę to klasa

public class MyInt 
{ 
    public bool CalledBlah { get; private set; } 

    public virtual void Blah() 
    { 
     this.CalledBlah = true; 
    } 
} 

Możesz napisać test w ten sposób:

[Test] 
public void Test_MyRealBlah() 
{ 
    Mock<MyInt> m = new Mock<MyInt>(); 
    m.CallBase = true; 

    bool calledBlah = false; 
    m.When(() => !calledBlah) 
     .Setup(i => i.Blah()) 
     .Callback(() => { calledBlah = true; m.Object.Blah(); }) 
     .Verifiable(); 

    m.Object.Blah(); 

    Assert.IsTrue(m.Object.CalledBlah); 
    m.VerifyAll(); 
} 

Kluczowym aspektem jest to, że można śledzić, czy fałszywy wersja została wywołana, a następnie ustaw makietę na , a nie wywołaj fałszywkę, jeśli została już wywołana.

Nadal można zrobić coś podobnego, jeśli wziąć args i spraw wartości:

public class MyInt 
{ 
    public List<int> CalledBlahArgs { get; private set; } 

    public MyInt() 
    { 
     this.CalledBlahArgs = new List<int>(); 
    } 

    public virtual void Blah(int a) 
    { 
     this.CalledBlahArgs.Add(a); 
    } 
} 

[Test] 
public void Test_UpdateQueuedGroups_testmockcallback() 
{ 
    Mock<MyInt> m = new Mock<MyInt>(); 
    m.CallBase = true; 

    List<int> fakeBlahArgs = new List<int>(); 

    m.Setup(i => i.Blah(It.Is<int>(a => !fakeBlahArgs.Contains(a)))) 
     .Callback<int>((a) => { fakeBlahArgs.Add(a); m.Object.Blah(a); }) 
     .Verifiable(); 

    m.Object.Blah(1); 

    Assert.AreEqual(1, m.Object.CalledBlahArgs.Single()); 
    m.Verify(b => b.Blah(It.Is<int>(id => 1 == id))); 
} 
+0

Interesujące. Jednakże ustawiłeś 'm.CallBase = true;', ale osoba pytająca wyraźnie powiedziała, że ​​chce rozwiązania, w którym nie ustawiasz 'CallBase' na True, ponieważ wpływa to także na wszystkich innych' wirtualnych' członków, a osoba pytająca chciała unikaj tego. –

Powiązane problemy