Pierwszy raz próbując napisać test jednostkowy dla metody wykorzystującej strukturę DateTime napotykamy na problem z odczytem aktualnego czasu z statycznych właściwości (UtcNow, Now, Today) struktury DateTime.  Jeśli logika testowanej metody zależy od aktualnego czasu lub daty, wynik testu będzie się różnić w zależności od czasu przeprowadzenia testu. Jak zarządzać aktualnym czasem w testach jednostkowych? Problem był, jest i będzie poruszany w sieci, poniżej prezentuję możliwe rozwiązania problemu.

Wrapper DateTime

W powyższym rozwiązaniu definiuję się interfejs dla statycznych właściwości i tworzy się dedykowaną implementacje interfejsu IDataTimeProvider. Następnie wstrzykuje się powyższą implementację interfejsu do klasy i podmienia się wywołania DateTime na DataTimeProvider.

W ramach testu jednostkowego wstrzykuje się mocka dla interfejsu IDataTimeProvider do testowanej klasy. Problem rozwiązany, jednak od razu rzuca się w oczy, że powiększamy klasę o kolejną zależność, którą trzeba wstrzyknąć np. przez konstruktor.

Static Provider

Kolejną opcją jest implementacja statycznej klasy, która będzie umożliwiała podmianę logiki obsługi aktualnego czasu na potrzebę testów jednostkowych.

Powyższe rozwiązanie można rozbudować dodając metodę do ustawiania i przywracania domyślnej logiki związanej z obsługą czasu.

Logika klasy DataTimeProvider rozbudowała się o metody, które tylko będą wykorzystywane do testów jednostkowych. Osobiście wolę wersje bez tej dodatkowej logiki. W obu przypadkach podmieniamy metodę w delegacie na potrzeby wykonania scenariusza testowego.

Ambient Context

Ostatnim rozwiązaniem w celu opanowania czasu jest wykorzystanie abstrakcyjnej klasy DateTimeProvider, którą opisuje w swojej książce Dependency Injection in .NET Mark Seemann. Klasa DateTimeProvider zwraca aktualnie przypisaną instancje do właściwości Current, domyślnie jest to instancja DefaultDateTimeProvider. Za każdym razem gdy odwołujemy się do właściwości np. UtcNow zwracana jest wartość utworzona przez aktualną instancje DateTimeProvider.

W implementacji DefaultDateTimeProvider przesłonięte są właściwości odnoszące się do aktualnego czasu. Przykład użycia DateTimeProvider można zobaczyć poniżej.

Na potrzeby testów jednostkowych do właściwości Current przypisujemy mocka, poniżej przykład z wykorzystaniem NSubstitute.

Podsumowanie

W przedstawionych przykładach założono, że nie ma kodu wielowątkowego i nikogo nie korci podmiana logiki biznesowej obsługi czasu w kodzie produkcyjnym. Wszystkie powyższe podejścia umożliwiają sterowanie aktualnym czasem na potrzeby testów jednostkowych. A jaki sposób jest u was wykorzystywany ?