O bibliotece AutoFixture wspominałem już ostatnio we wpisie Lightning talk – Autofixture. Biblioteka ta pomaga nam w tworzeniu obiektów w trakcie fazy [mark]Arrange[/mark] w testach jednostkowych. Dzięki niej możemy skupić się na tym co rzeczywiście ma zostać przetestowane, a nie na tworzeniu obiektów, które są potrzebne do przeprowadzenia testu. Dodatkowo chroni ona nas przed niepotrzebną modyfikacją testów w momencie zmiany wykorzystywanych w testach obiektów.

Ma ona drobną wadę, w zależności od obiektów, które tworzymy za jej pomocą może ona spowodować znaczące wydłużenie trwania testów jednostkowych. W szczególności, gdy za jej pomocą tworzymy obiekty związane z EntityFrameworkiem.

Oczywiście jest na ten problem rozwiązanie, którym chciałbym się z Wami podzielić. Musimy napisać kod, który spowoduje, że AutoFixture nie będzie generował obiektów, które oznaczone są jako virtual w klasach. Aby to zrobić należy po pierwsze zaimplementować własnego SpecimenBuilder-a:

public class IgnoreVirtualMembersSpecimenBuilder : ISpecimenBuilder
{
  public object Create(object request, ISpecimenContext context)
  {
    var propertyInfo = request as PropertyInfo;
    if (propertyInfo == null)
    {
      return new NoSpecimen();
    }

    if (propertyInfo.GetGetMethod().IsVirtual)
    {
      return null;
    }

    return new NoSpecimen();
  }
}

A następnie odpowiednio skonfigurować AutoFixture:

var fixture = new Fixture();
fixture.Customizations.Add(new IgnoreVirtualMembersSpecimenBuilder());

Rozwiązanie to działa w większości przypadków. Należy tylko pamiętać, że w momencie implementacji interfejsu przez jakąś klasę CLR domyślnie oznaczy elementy wskazane w interfejsie jako virtual i przez to nie zostaną one wygenerowane przez tak skonfigurowany Fixture. Patrząc na poniższy przykład:

public interface IUser
{
  string Name { get; set; }
  string Surname { get; set; }
}

public class User : IUser
{
  public string Name { get; set; }
  public string Surname { get; set; }
}

Należy zauważyć, że zarówno do właściwości Name, jak i Surname zostanie przypisana wartość null. Zobaczcie sami i spójrzcie na poniższy test jednostkowy:

[Fact]
public void IssueWithClassThatImplementsInterface()
{
  // Arrange
  var fixture = new Fixture();
  fixture.Customizations.Add(new IgnoreVirtualMembersSpecimenBuilder());

  // Act
  var user = fixture.Create<User>();

  // Assert
  Assert.Null(user.Name);
  Assert.Null(user.Surname);
}

Opisane rozwiązanie pozwoli zaoszczędzić nam sporo czasu w przypadku budowania złożonych obiektów, w szczególności tych powiązanych z EntityFrameworkiem.