Wednesday, September 29, 2010

Fluent Validation Part III – The Repository

This is the fourth installment in this series:

  • Introduction – The basics or what Fluent Validation is
  • Part I – TDD with Fluent Validation and string validation
  • Part II – Date validation and custom property validators

This time we will take a look at how we can take advantage of a repository to help us with our data validation. Using a repository helps separate our data access concern from our model, allows our architecture to be more flexible, and maximizes the amount of testable code by abstracting the data layer.

We will continue to build upon the project we have been using from the beginning, so let’s get started by firing up Visual Studio, and opening our FluentValidation.Example.Solution. Open the Employee class and add a new property SocialSecurityNumber:

public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string SocialSecurityNumber { get; set; }
}
Next, we need to create an interface for our employee repository. Add a new interface to the Models project called IEmployeeRepository:
public interface IEmployeeRepository
{
IQueryable<Employee> Select();
void Save(Employee item);
void Delete(Employee item);
bool IsUnique(Employee item);
}
We are going to use the IsUnique method to validate that we are not persisting two Employees with the same social security number. In order the help us with the test, we need some data. Add a new class called FakeEmployeeRepository to the UnitTests propject:
public class FakeEmployeeRepository : IEmployeeRepository
{
private List<Employee> data;

public FakeEmployeeRepository()
{
LoadData();
}

private void LoadData()
{
data
= new List<Employee>
{
new Employee{ ID=1, SocialSecurityNumber = "123-45-6789" }
};
}

public IQueryable<Employee> Select()
{
return data.AsQueryable();
}

public void Save(Employee item)
{
throw new NotImplementedException();
}

public void Delete(Employee item)
{
throw new NotImplementedException();
}

public bool IsUnique(Employee item)
{
return !data.Where(HasMatchingSocialSecurityNumber(item)).Any();
}

private Func<Employee, bool> HasMatchingSocialSecurityNumber(Employee item)
{
return x => !x.ID.Equals(item.ID) && x.SocialSecurityNumber.Equals(item.SocialSecurityNumber);
}
}
We need to update both our EmployeeValidatorTests and our EmployeeValidator classes:
[TestInitialize]
public void Init()
{
//arrange
target = new Employee();
validator
= new EmployeeValidator(new FakeEmployeeRepository());
}
public EmployeeValidator(IEmployeeRepository repository)
{
RuleFor(x
=> x.FirstName)
.NotEmpty()
.WithMessage(
"First name is required.")
.Length(
0, 50)
.WithMessage(
"First name cannot exceed 50 characters.")
.Matches(
@"^[A-Za-z\-\.\s]+$")
.WithMessage(
"First name contains invalid characters.");
}
It is time for us to write some tests that will exercise our new functionality. Add the following tests to the EmployeeValidatorTests class:
[TestMethod]
public void SocialSecurityNumber_NonUniqueValue_ValidationFails()
{
//arrange
target.SocialSecurityNumber = "123-45-6789";

//act
ValidationResult result = validator.Validate(target, x => x.SocialSecurityNumber);

//assert
validator.ContainsRuleFor<Employee>(x => x.SocialSecurityNumber);
result.AssertValidationFails(
"Social security number must be unique.");
}

[TestMethod]
public void SocialSecurityNumber_UniqueValue_ValidationPasses()
{
//arrange
target.SocialSecurityNumber = "987-65-4321";

//act
ValidationResult result = validator.Validate(target, x => x.SocialSecurityNumber);

//assert
validator.ContainsRuleFor<Employee>(x => x.SocialSecurityNumber);
result.AssertValidationPasses();
}
Let’s run these tests the ensure they fail. Add a new custom rule to our EmployeeValidator that uses our repository:
public class EmployeeValidator : AbstractValidator<Employee>
{
private IEmployeeRepository repository;

public EmployeeValidator(IEmployeeRepository repository)
{
this.repository = repository;

RuleFor(x
=> x.FirstName)
.NotEmpty()
.WithMessage(
"First name is required.")
.Length(
0, 50)
.WithMessage(
"First name cannot exceed 50 characters.")
.Matches(
@"^[A-Za-z\-\.\s]+$")
.WithMessage(
"First name contains invalid characters.");

RuleFor(x
=> x.SocialSecurityNumber)
.Must(BeAUniqueSocialSecurityNumber)
.WithMessage(
"Social security number must be unique.");
}

private bool BeAUniqueSocialSecurityNumber(Employee item, string socialSecurityNumber)
{
return repository.IsUnique(item);
}
Now let’s run our tests for the last time and watch with happy hearts the green light of testing Nirvana.

I hope you have enjoyed this series and learned some of the techniques available for simple and complex validation, business rules, and how to unit test them. If you have any questions or if I’ve left any loose ends, feel free to contact me. Thank you and happy coding!

2 comments:

  1. Question: As this post shows, it is possible to unit test validation. Is it possible to test observable collections?

    ReplyDelete
  2. This is great stuff. I love it.

    ReplyDelete