Chciałem Was zachęcić do używania biblioteki, którą ostatnio opublikowałem – FluentValidation.Validators.UnitTestExtension. Głównym jej celem jest rozszerzenie oraz uproszczenie możliwości testowania kodu, który wykorzystuje pakiet FluentValidation do walidacji obiektów.

Bibliotekę można zainstalować wykorzystując nugeta:


Install-Package FluentValidation.Validators.UnitTestExtension

lub ściągnąć kod z GithHuba.

Na wiki dotyczącej biblioteki FluentValidation można znaleźć propozycję pisania testów przygotowaną przez autorów biblioteki. W celu ułatwienia nam tego procesu autorzy przygotowali dwie metody ShouldHaveValidationErrorFor oraz ShouldNotHaveValidationErrorFor.

Testy jednostkowe je wykorzystujące wyglądają w następujący sposób:

readonly PersonValidator validator = new PersonValidator();

[Fact]
public void Given_FirstNameIsNull_When_Validating_Then_Error()
{
  validator.ShouldHaveValidationErrorFor(person => person.FirstName, null as string);
}

[Fact]
public void Given_FirstNameIsEmpty_When_Validating_Then_Error()
{
  validator.ShouldHaveValidationErrorFor(person => person.FirstName, string.Empty);
}

[Fact]
public void Given_FirstNameIsToLong_When_Validating_Then_Error()
{
  validator.ShouldHaveValidationErrorFor(person => person.FirstName,
                                         "Long_Test_More_Than_20_Characters");
}

[Fact]
public void Given_CorrectFirstName_When_Validating_Then_NoError()
{
  validator.ShouldNotHaveValidationErrorFor(person => person.FirstName, "John");
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("Long_Test_More_Than_20_Characters")]
public void Given_NotCorrectLastName_When_Validating_Then_Error(string notAcceptedText)
{
  validator.ShouldHaveValidationErrorFor(person => person.LastName, notAcceptedText);
}

[Fact]
public void Given_CorrectLastName_When_Validating_Then_NoError()
{
  validator.ShouldNotHaveValidationErrorFor(person => person.LastName, "John");
}

[Theory]
[InlineData(0)]
[InlineData(-10)]
[InlineData(260)]
public void Given_NotCorrectHeight_When_Validating_Then_Error(int height)
{
  validator.ShouldHaveValidationErrorFor(person => person.HeightInCentimeters, height);
}

[Fact]
public void Given_CorrectHeight_When_Validating_Then_NoError()
{
  validator.ShouldNotHaveValidationErrorFor(person => person.HeightInCentimeters, 150);
}

Przy tym podejściu należy zwrócić uwagę, że nie testujemy tylko naszego kodu, ale również biblioteki FluentValidation. Musimy podać wszystkie warunki przy których walidacja jest poprawna lub też błędna. Testy te bardziej są testami integracyjnymi niż testami jednostkowymi.

Dodatkowo razem z biblioteką dostarczana jest metoda:

validator.ShouldHaveChildValidator(x => x.Address, typeof(AddressValidator));

Jednakże cały czas to jest dla mnie za mało. Chciałem, aby tego typu testy można było pisać prosto oraz szybko. Z tego powodu powstała ta biblioteka. Z jej wykorzystaniem te same testy wyglądają w następujący sposób:

public class PersonValidatorTests
{
  // Act
  readonly PersonValidator personValidator = new PersonValidator();

  [Fact]
  public void Given_When_PersonValidatorConstructing_Then_3PropertiesShouldHaveRules()
  {
    // Assert
    personValidator.ShouldHaveRulesCount(3);
  }

  [Fact]
  public void Given_When_PersonValidatorConstructing_Then_RulesForFirstNameAreConfiguredCorrectly()
  {
    // Assert
    personValidator.ShouldHaveRules(x => x.FirstName,
      BaseVerifiersSetComposer.Build()
        .AddPropertyValidatorVerifier<NotNullValidator>()
        .AddPropertyValidatorVerifier<NotEmptyValidator>()
        .AddPropertyValidatorVerifier<LengthValidator>(0, 20)
        .Create());
  }

  [Fact]
  public void Given_When_PersonValidatorConstructing_Then_RulesForLastNameAreConfiguredCorrectly()
  {
    // Assert
    personValidator.ShouldHaveRules(x => x.LastName,
      BaseVerifiersSetComposer.Build()
        .AddPropertyValidatorVerifier<NotNullValidator>()
        .AddPropertyValidatorVerifier<NotEmptyValidator>()
        .AddPropertyValidatorVerifier<LengthValidator>(0, 20)
        .Create());
  }

  [Fact]
  public void Given_When_PersonValidatorConstructing_Then_RulesForHeightAreConfiguredCorrectly()
  {
    // Assert
    personValidator.ShouldHaveRules(x => x.HeightInCentimeters,
      BaseVerifiersSetComposer.Build()
        .AddPropertyValidatorVerifier<GreaterThanValidator>(0)
        .AddPropertyValidatorVerifier<LessThanOrEqualValidator>(250)
        .Create());
  }
}

Jak widzicie są one prostsze, krótsze oraz czytelniejsze. Dodatkowo powinny one działać szybciej ponieważ testują one tylko kod, który został napisany przez nas, a nie wykonują kodu FluentValidation.