I have still another version of my validation extensions. I've reworked it and added some new features. It doesn't relay on expression trees any more but as a compensation the same extensions can be used for unit testing.
The base class is still the ValidationContext
:
public class ValidationContext<TArg>
{
private string _memberName;
public TArg Argument { get; internal set; }
public string MemberName
{
get
{
if (!string.IsNullOrEmpty(_memberName)) return _memberName;
var memberExpression = Expression().Body as MemberExpression;
return memberExpression.Member.Name;
}
set { _memberName = value; }
}
internal Func<Expression<Func<TArg>>> Expression { get; set; }
public virtual ValidationContext<TArg> Validate<TException>(
Func<TArg, bool> predicate,
params object[] args)
where TException : Exception
{
if (!predicate(Argument))
{
throw (TException)Activator.CreateInstance(typeof(TException), args);
}
return this;
}
}
however in this version it is the one that throws exceptions after checking a validation rule. The Validate
method can be overriden in a derived class which allows to create a new validation context for unit testing. It is now able to throw a different kind of exception that the unit testing environment will notice. It forwards the original exception as the inner one.
public class UnitTestingValidationContext<TArg> : ValidationContext<TArg>
{
public override ValidationContext<TArg> Validate<TException>(
Func<TArg, bool> predicate,
params object[] args)
{
try
{
if (!predicate(Argument))
{
throw (TException)Activator.CreateInstance(typeof(TException), args);
}
return this;
}
catch (Exception inner)
{
// cannot throw this in linqpad
//throw new AssertFailedException
throw new Exception("This is a test.", inner);
}
}
}
As a result the Validations
extensions have changed a little and have gotten a new Test
method and refactored validations:
public static class Validations
{
// crates validation context for normal usage
public static ValidationContext<TArg> Validate<TArg>(
this TArg arg,
string memberName)
{
return new ValidationContext<TArg>()
{
Argument = arg,
MemberName = memberName
};
}
// create validation context for unit-testing
public static UnitTestingValidationContext<TArg> Test<TArg>(
this TArg arg,
string memberName)
{
return new UnitTestingValidationContext<TArg>()
{
Argument = arg,
MemberName = memberName
};
}
// validations don't throw exceptions anymore but tell the context how to do it
public static ValidationContext<TArg> IsNotNull<TArg>(
this ValidationContext<TArg> context)
{
return context.Validate<ArgumentNullException>(arg =>
arg != null, context.MemberName, "Test message.");
}
public static ValidationContext<string> IsNotNullOrEmpty(
this ValidationContext<string> context)
{
return context.Validate<ArgumentNullException>(arg =>
!string.IsNullOrEmpty(context.Argument), context.MemberName, "Test message.");
}
}
Examples:
var foo = (string)null;
// normal usage like for method parameters etc.
foo.Validate("foo").IsNotNullOrEmpty(); // bam!
// in unit-testing
foo.Test("foo").IsNotNullOrEmpty(); // bam!
Some more examples showing how I mean to use the extensions (this is a special case where I wrap the validation in an Action
because it looks nice when alls tests look the same):
[TestMethod]
public void IsNullPasses()
{
new Action(() => ((string)null).Validate().IsNull()).Test().DoesNotThrow();
}
[TestMethod]
public void IsNullThrows()
{
new Action(() => "".Validate().IsNull()).Test().Throws<ArgumentException>();
}
internal
setter? You don't see that very often. \$\endgroup\$var
s near each other it may make the code readable if the type information is consistently on the right. \$\endgroup\$var x = (string)null;
really look prettier thanstring x = null;
? Really?? I use var all the time, but imo a cast always makes things look uglier, so if that's required I'd use the type name explicitly. \$\endgroup\$