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; }
}
public interface IEmployeeRepository
{
IQueryable<Employee> Select();
void Save(Employee item);
void Delete(Employee item);
bool IsUnique(Employee item);
}
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);
}
}
[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.");
}
[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();
}
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);
}
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!
Question: As this post shows, it is possible to unit test validation. Is it possible to test observable collections?
ReplyDeleteThis is great stuff. I love it.
ReplyDelete