Po poprzednim odcinku potrafimy już dowolnie weryfikować i konfigurować wartości parametrów dla metod. Dzisiaj z kolei poustawiamy akcje, które mają się w momencie wywołania metod wykonać. Jest to czynność zdecydowanie prostsza, ponieważ właściwie wszystko mamy w Intellisense. Zobaczmy:
Zwracana wartość
Najprostsza możliwa konfiguracja to ustawienie wartości zwracanej przez metodę. Obiekt implementujący interfejs IMethodOptions<T> zwracany przez Stub(…) posiada konstrukcję Return(…) przyjmującą wartość, która zostanie zwrócona przez konfigurowaną akcję. W poniższy sposób zasymulujemy udane uwierzytelnienie dla podanego loginu i hasła:
1: authenticationService.Stub(x => x.Authenticate(userName, password)).Return(true);
Wyjątek
Inny banalny scenariusz to rzucenie wyjątku przez metodę. W ten sposób możemy zbadać zachowanie naszego kontrolera w sytuacji, w której usługa uwierzytelniająca bardzo dosadnie powie “coś jest nie tak!”:
1: authenticationService.Stub(x => x.Authenticate(userName, password)) 2: .Throw(new AuthenticationException());
Liczba powtórzeń
Pouczającym i bardziej skomplikowanym niż inne obszary przykładem wzorca Fluent Interface jest możliwość definiowania zachowania akcji daną liczbę razy. Poniższy kod zwróci true dla dwóch pierwszych wywołań, a w razie braku dalszej konfiguracji, wszystkie kolejne wywołania metody Authenticate będą zwracały wartość domyślną, czyli false:
1: authenticationService.Stub(x => x.Authenticate(userName, password)) 2: .Repeat.Twice() 3: .Return(true);
Taka konstrukcja mówi, że metoda nie powinna zostać wywołana ani razu (choć nigdy nie zdarzyło mi się z tego korzystać) – wywołanie skończy się wyjątkiem na poziomie RhinoMocks, co spowoduje oblanie testu:
1: authenticationService.Stub(x => x.Authenticate(userName, password)) 2: .Repeat.Never();
Zaawansowane operacje
W przypadku bardziej zaawansowanych scenariuszy możemy natknąć się na konieczność odroczenia zdefiniowania zwracanej wartości do momentu, gdy znane będą parametry wywołania. Składania w RhinoMocks nie wygląda w tym przypadku ślicznie, ale da się coś takiego osiągnąć. Przykładowo poniższy kod zasymuluje powodzenie uwierzytelnienia jeśli login i hasło są takie same:
1: authenticationService.Stub(x => x.Authenticate(null, null)) 2: .IgnoreArguments().Return(false) 3: .WhenCalled(opts => opts.ReturnValue = opts.Arguments[0] == opts.Arguments[1]);
Zwróćmy uwagę na środkową linijkę. Najpierw określamy, że definiowana akcja ma wykonać się niezależnie od wartości przekazanych do metody (IgnoreArguments()), a następnie jawnie definiujemy (dowolną) wartość zwracaną przez metodę. Dopiero potem, na koniec, nadpisujemy ReturnValue w wyrażeniu lambda przekazanym do metody WhenCalled() wołanej podczas określania czynności do wykonania przez RhinoMocks – mamy już wtedy dostęp do wartości parametrów.
Poniższy przykład również korzysta z tego mechanizmu. Chcemy, aby każdorazowe pobranie użytkownika ze stuba implementującego interfejs IUsersRepository zwróciło nową instancję klasy User z odpowiednio ustawionym Id:
1: usersRepository.Stub(x => x.GetUser(0)).IgnoreArguments() 2: .IgnoreArguments().Return(null) 3: .WhenCalled(opts => opts.ReturnValue = new User {Id = (int) opts.Arguments[0]});
Kolejny obszar RhinoMocks i testowania z mockami – odkryty:). Następnym razem zerkniemy na zdarzenia w kontekście testów jednostkowych.