Na dzień dzisiejszy wybierając “mocking framework” stawiam właśnie na fakeiteasy. Ma ona jeden ciemny zakamar, w którym można nieźle pobłądzić… a jest to testowanie wywołania settera.
Załóżmy, że mamy jakiś interfejs wymuszający na implementacjach posiadanie właściwości Age:
1: public interface IHaveAge 2: { 3: int Age { get; set; } 4: }
I test “jakiejś klasy” przetwarzającej implementacje tego interfejsu, mającej ustawić wartość wspomnianej właściwości:
1: [Fact] 2: public void sets_age_to_18_during_processing() 3: { 4: var withAge = A.Fake<IHaveAge>(); 5: 6: new TestedClass().Process(withAge); 7: 8: A.CallTo(() => withAge.Age = 18) 9: .MustHaveHappened(); 10: }
Wydaje się w porządku? Otóż nie jest. Kompilator zgłosi, że jesteśmy fe:
Error 5 An expression tree may not contain an assignment operator
Sam powyższy scenariusz jest jeszcze w miarę prosty, bo przypisanie konkretnej wartości do settera możemy przetestować w “normalny” sposób – fakeiteasy zapamiętuje przypisanie i konfiguruje mocka tak aby zwracał aktualnie nadaną właściwości wartość:
1: [Fact] 2: public void sets_age_during_processing() 3: { 4: var withAge = A.Fake<IHaveAge>(); 5: 6: new TestedClass().Adapt(withAge); 7: 8: Assert.Equal(18, withAge.Age); 9: }
Ale problem pojawia się, jeśli chcemy przetestować sam fakt wywołania settera, a nie jego aktualną wartość. Sytuacja taka może być na nas wprost wymuszona, na przykład jeśli mockujemy interfejs z zewnętrznej biblioteki i ma on właściwość write-only, bez gettera (rzadki przypadek, ale się zdarza). Jedynym chyba sposobem na napisanie takiego testu w fakeiteasy jest poniższy brzydal:
1: [Fact] 2: public void sets_age_during_processing() 3: { 4: var withAge = A.Fake<IHaveAge>(); 5: 6: new TestedClass().Adapt(withAge); 7: 8: A.CallTo(withAge) 9: .Where(x => 10: x.Method.Name == "set_Age" 11: // if we want to test the value as well 12: // && x.GetArgument<int>(0) == 18 13: ).MustHaveHappened(); 14: }
Okropne, ale… nie znalazłem lepszego sposobu (nie tylko ja, wiem że i Gutek trochę nad tym posiedział…).
Co do 3. przypadku, to brzydka wydaje mi się koncepcja interfejsu z setterem typu write-only (nie udostępniającego sposobu na sprawdzenie skutków takiego wywołania). Nie dziwi mnie dlatego, że konfiguracja mocka jest w tym wypadku równie brzydka :]
dooh,
Prawda, chociaż np w Moq da się do zapisać ładniej, bez stringów ( http://stackoverflow.com/questions/1641919/moq-how-to-verify-that-a-property-value-is-set-via-the-setter ).