- atomowy (testujemy tylko jeden fragment funkcjonalności)
- deterministyczny
- powtarzalny
- niezależny od kolejności wywoływania innych testów
- szybki (czas wykonania rzędu milisekund)
- łatwy do uruchomienia
W tym przypadku z pomocą przychodzi zasada Dependency Inversion. Zamiast wstrzykiwać do testowanego serwisu właściwe zależności, możemy na etapie UT wstrzyknąć inną implementację danego interfejsu wykonującą tylko tyle kodu, ile potrzebujemy. Problem polega na tym, że wraz ze wzrostem komplikacji właściwych serwisów musimy także utrzymywać kod mockowanych serwisów.
Tu z pomocą przychodzi Rhino Mocks - framework budujący dynamiczne mocki (wykorzystując obiekty proxy) na potrzeby naszych testów.
Mockowanie rozpoczynamy od użycia klasy MockRepository, która stworzy nam mock dla dowolnego interfejsu. Mock taki będzie zawierał puste metody (dla metod zwracających void) lub metody zwracające wartość domyślną dla pozostałych metod z interfejsu.
Przykładowo mamy klasę InvoiceService, która woła jedną ze swoich zależności - InvoiceRepository.
public class Invoice { public string Id { get; set; } public decimal Amount { get; set; } public string UserId { get; set; } } public interface IInvoiceRepository { bool Store(Invoice entity); } public class InvoiceRepository : IInvoiceRepository { public bool Store(Invoice entity) { //Long running SQL operation return true; } } public class InvoiceService { private IInvoiceRepository _invoiceRepository; public InvoiceService(IInvoiceRepository invoiceRepository) { _invoiceRepository = invoiceRepository; } public void SaveInvoice(Invoice item) { var success = _invoiceRepository.Store(item); if(!success) throw new Exception("Unable to save"); } }
Chcemy zamockować interfejs IInvoiceRepository w ten sposób, by zwracał true, jeżeli podamy fakturę różną od null.
[Test] public static void InvoiceService_ShouldCallInvoiceRepository() { //Arrange IInvoiceRepository repositoryMock = MockRepository.GenerateMock<IInvoiceRepository>(); repositoryMock.Stub(x => x.Store(Arg<Invoice>.Is.NotNull)).Return(true); Debug.WriteLine(repositoryMock.GetType().FullName); var service = new InvoiceService(repositoryMock); //Act service.SaveInvoice(new Invoice()); //Assert repositoryMock.AssertWasCalled(s => s.Store(Arg<Invoice>.Is.Anything)); }
Na konsoli wypisze się: Castle.Proxies.IInvoiceRepositoryProxyc9859a0ce17d461faef8b8deb39a407c, ponieważ RhinoMocks korzysta z mechanizmu Castle.DynamicProxy.
Zamiast parametru Anything moglibyśmy podać referencję do obiektu faktury przekazywanej do metody SaveInvoice. Często zdarza się też tak, że metoda przyjmuje kilka parametrów, na podstawie których budowany jest obiekt. Tu z pomocą przychodzi mechanizm constraintów.
Rozszerzamy nasz serwis o dodatkową metodę:
public class InvoiceService { //... public void SaveInvoice(string id, string userId, decimal amount) { SaveInvoice(new Invoice() { Amount = amount, Id = id, UserId = userId }); } }
I za pomocą metody Matches podajemy warunki, jakie muszą być spełnione.
[Test] public static void InvoiceService_ShouldCallInvoiceRepository_AndSaveInvoiceWithSameProperties() { //Arrange IInvoiceRepository repositoryMock = MockRepository.GenerateMock<IInvoiceRepository>(); repositoryMock.Stub(x => x.Store(Arg<Invoice>.Is.NotNull)).Return(true); string id = "12"; string userId = "5"; decimal amount = 100; var service = new InvoiceService(repositoryMock); //Act service.SaveInvoice(id, userId, amount); //Assert repositoryMock.AssertWasCalled(s => s.Store(Arg<Invoice>.Matches(d => d.Amount == amount && d.UserId == userId))); }
Brak komentarzy:
Prześlij komentarz