Pora przyjrzeć się trzeciemu najpopularniejszemu darmowemu frameworkowi, obok Moq i FakeItEasy, do tworzenia atrap w .NET – NSubstitute.
Co wyróżnia tę bibliotekę:
Dzięki temu, że biblioteka jest lekka, łatwa i przyjemna, doskonale nadaje się do celów edukacyjnych w temacie TDD i tworzenia atrap.
Aby stworzyć atrapę w NSubstitute, należy skorzystać z metody For
klasy Substitute
:
ICustomer customer = Substitute.For<ICustomer>();
Przekazanie parametrów do konstruktora odbywa się przez ww. metodę For
. W niej definiujemy wszystkie argumenty konstruktora:
var customerValidator = Substitute.For<ICustomerValidator>();
var customerRepository = Substitute.For<CustomerRepository>(customerValidator);
Zamienniki w NSubstitute tworzone są w trybie “loose”. Dla przykładu:
ICustomer customer = Substitute.For<ICustomer>();
string firstName = customer.FirstName; // firstName = ""
Zachowanie zamiennika można zdefiniować za pomocą metody rozszerzającej Returns
:
ICustomer customer = Substitute.For<ICustomer>();
customer.GetAge().Returns(20);
Właściwości możemy definiować tak jak powyżej lub bezpośrednio przez dostęp do settera:
ICustomer customer = Substitute.For<ICustomer>();
customer.FirstName = "John";
customer.FirstName.Returns("John"); // ekwiwalentne do powyższego
Aby wyrzucić wyjątek, korzystamy z metody Throws
:
ICustomer customer = Substitute.For<ICustomer>();
customer.GetAge().Throws<NotSupportedException>();
“Callback” możemy wykonać poprzez metodę AndDoes
:
int a = 0;
ICustomer customer = Substitute.For<ICustomer>();
customer.GetAge()
.Returns(20)
.AndDoes(info => a++);
// a = 0
customer.GetAge();
// a = 1
Parametr metody AndDoes
jest typu Action<CallInfo>
, dzięki czemu mamy dostęp, dzięki klasie CallInfo
, do argumentów przekazanych metodzie.
Do zweryfikowania wywołania zamienników wykorzystuje się metodę Received
oraz DidNotReceive
:
var customerValidator = Substitute.For<ICustomerValidator>();
var customerRepository = new CustomerRepository(customerValidator);
var customer = Substitute.For<ICustomer>();
customer.FirstName = "John";
customerRepository.Add(customer);
customerValidator.Received().Validate(customer);
Received
przyjmuje opcjonalnie jako parametr ilość oczekiwanych wywołań.
“Argument matchery” działają w podobny sposób co w Moq i FakeItEasy. Parametry metody możemy przekazać bezpośrednio:
calculator.Add(1, 2).Returns(3);
Lub, w przypadku złożonego dopasowania argumentów, za pomocą klasy Arg
i metody Any
oraz Is
:
calculator.Add(Arg.Any<int>(), Arg.Is<int>(i => i > 0)).Returns(1);
Sposób obsługi naszych atrap jest o wiele prostszy i czytelniejszy w stosunku do Moq i FakeItEasy. Semantyka jest do bólu prosta i nie pozostawia zbyt wielu domysłów czytelnikowi. Wykorzystanie lambd zostało zminimalizowane, przez co kod jest też łatwiejszy do zrozumienia dla początkujących programistów zaczynających przygodę z C# i TDD.
Do tej pory poznaliśmy trzy biblioteki do tworzenia atrap – Moq, FakeItEasy oraz NSubstitute. Wszystkie mają otwarty kod źródłowy i są darmowe. Warto poeksperymentować z nimi, aby wyrobić sobie opinię na temat każdej z nich. Ja osobiście nie mam faworyta w tej kwestii, uznałbym że wszystkie trzy zajmują ex aequo pierwsze miejsce. Różnica jest nieznaczna, gdyż wszystkie trzy oparte są na wzorcu proxy (na jednym z ich wariancie) i bibliotece Castle.Core.
Jest jeszcze kilka innych konkurentów na rynku, w tym płatnych i bardziej zaawansowanych. Nie będę ich jednak opisywać w tak szczegółowy sposób jak wyżej wymienione; niemniej jednak, temat powróci przy okazji omówienia typów bibliotek do tworzenia atrap i zaglądnięcia nieco pod maskę.
Część I: Testy jednostkowe – wstęp
Część II: Atrapy obiektów
Część III: Teoria