Mockowanie IEnumerable/IEnumerator z fakeiteasy

7

Niedawno po raz pierwszy w życiu musiałem mockować implementację interfejsu IEnumerable<T>. Chodziło o jakieś dziwne struktury używane wewnętrznie przez FIM. Problem polegał na tym, że obiekt mockowanego przeze mnie typu zwracał kolekcję innych obiektów. Ta kolekcja była właśnie IEnumerable<X>… ale nie mogłem stworzyć jej instancji, ponieważ wspomniana klasa XCollection była abstrakcyjna, a jej implementacja siedziała zaszyta gdzieś wewnątrz jakichś dllek. Jednocześnie chciałem przetestować swój kod, podając mu tak spreparowany mock, aby dało się jeździć po XCollection pętlą foreach oraz LINQ (które to stwierdzenia znaczą właściwie to samo).

Okazało się, że trzeba było trochę pogłówkować. Poniżej kod realizujący to zadanie, od razu z testami sprawdzającymi poprawność stworzonego mocka:

  1:  private IEnumerable<T> create_fake_enumerable<T>(params T[] items)
  2:  {
  3:      var enumerable = A.Fake<IEnumerable<T>>();
  4:      var enumerator = A.Fake<IEnumerator<T>>();
  5:  
  6:      A.CallTo(() => enumerable.GetEnumerator())
  7:          .Returns(enumerator);
  8:  
  9:      // fake configuration is LIFO ->
 10:      // -> define last action first
 11:      A.CallTo(() => enumerator.MoveNext())
 12:          .Returns(false)
 13:          .Once();
 14:  
 15:      // define true for each element in enumerator
 16:      // overriding earlier configuration
 17:      A.CallTo(() => enumerator.MoveNext())
 18:          .Returns(true)
 19:          .NumberOfTimes(items.Length);
 20:  
 21:      A.CallTo(() => enumerator.Current)
 22:          .ReturnsNextFromSequence(items);
 23:  
 24:      return enumerable;
 25:  }
 26:  
 27:  [Fact]
 28:  public void is_usable_in_linq()
 29:  {
 30:      var enumerable = create_fake_enumerable(3, 6, 9);
 31:  
 32:      var between4and9 = enumerable.Where(x => x > 4 && x < 9);
 33:  
 34:      Assert.Equal(1, between4and9.Count());
 35:  }
 36:  
 37:  [Fact]
 38:  public void is_usable_in_foreach()
 39:  {
 40:      var enumerable = create_fake_enumerable(6, 6, 6, 6);
 41:  
 42:      int counter = 0;
 43:  
 44:      foreach (var number in enumerable)
 45:      {
 46:          Assert.Equal(6, number);
 47:          counter++;
 48:      }
 49:  
 50:      Assert.Equal(4, counter);
 51:  }
Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

7 Comments

  1. czy mógłbyś przybliżyć przewagę stowrzonego mocka nad rzutowaniem argumentu items na IEnumerable<T>?

  2. wiero,
    Chyba nie rozumiem pytania. Rzutowaniem argumentu w metodzie create_fake_enumerable, czy w samym teście? Jeśli w samym teście to tak jak napisałem we wstępie – nie miałem takiej możliwości. Rzadka sytuacja i przydarzyła mi się pierwszy raz, ale fantacja grupy produktowej FIM jest nieograniczona:).

  3. chodziło mi o metodę create_fake_enumerable, nie do końca załapałem czemu tworzony jest jest mock jeśli sama T[] może zachowywać się jak IEnumerable<T>

  4. wiero,
    Hmm… muszę się nad tym zastanowić. To by było strasznie głupie, gdybym robił takie konstrukcje bez potrzeby:). Implementowałem to ze 2 miesiące temu, postaram się wieczorem sprawdzić dlaczego robiłem tak a nie inaczej i odpiszę.

  5. OK, już mam. Musiałem zamockować właściwość "attribute.Values", gdzie "Values" jest typu "ValueCollection". Ten typ implementuje IEnumeratble (niegeneryczny) i jest to klasa abstrakcyjna – nie mogę z niej bezpośrednio skorzystać. Jej implementacja jest gdzieś głęboko ukryta – nie mam do niej dostępu z poziomu testów. Jej metoda GetEnumerator zwraca ValueCollectionEnumerator – też klasa abstrakcyjna, też nie mam dostępu do implementacji.

    Faktycznie może stąd pewien "mismatch" między moim przykładem a faktycznym zastosowaniem "w życiu" – w prawdziwych warunkach nie mogłem ani niczego rzutować na jawnie dostępny typ. Masz całkowitą rację – w przykładzie spokojnie można by było rzutować na IEnumerable<T> i spokój. W mojej konkretnej sytuacji nie było to IEnumerable<T>, tylko ValueCollection i ValueCollectionEnumerator…

  6. Banalne, ale intrygujące mnie pytanie: dlaczego nazwy metod rozpoczynasz od małej litery?

  7. Tomek,
    A jakoś tak mi się przyjęło że w testach używam "notacji_podkreśleniowej":). Post na ten m.in. temat jest w trakcie tworzenia, powinien się w tym tygodniu ukazać.