computers are cool

mookid on code

Helping future me

April 15th, 2009 by mookid

Usually, when writing code, you adhere to some conventions on how your stuff should work. At least I hope you do – otherwise your code is probably a mess!

Sometimes, these conventions can be enforced by using some patterns to allow for compile-time checking – one example, that I can think of right now, is using the visitor pattern to implement multiple dispatch, which is explored a little bit in another post.

But what about conventions, that can only be checked at runtime? Well, how do we usually check stuff that can only be checked at runtime? – by writing tests, of course!

One project I am currently involved in, is written in ASP.NET MVC. All form posts are done using the automatic binding features of the framework, and I am following the convention that the names of my view models should end in “Form” – so as to enabling me to easily distinguish my form posting DTOs from my other view models. What is more natural, then, than performing the following test:

[Test]
public void ActionParametersAreEitherPrimitiveTypesOrTruePocos()
{
	var assembly = Assembly.GetAssembly(typeof (HomeController));
	var types = assembly.GetTypes().ToList();
 
	types
		.FindAll(ThatIsConcreteController)
		.SelectMany(t => t.GetMethods().ToList().FindAll(ControllerActions))
		.ToList()
		.ForEach(CheckMethodInfo);
}
 
bool ControllerActions(MethodInfo info)
{
	return info.IsPublic && typeof(ActionResult).IsAssignableFrom(info.ReturnType);
}
 
void CheckMethodInfo(MethodInfo info)
{
	var parameters = info.GetParameters().ToList();
 
	parameters.ForEach(p => CheckParameterType(info, p));
}
 
void CheckParameterType(MethodInfo methodInfo, ParameterInfo parameterInfo)
{
	Assert.IsTrue(IsPrimitiveType(parameterInfo) || IsFormType(parameterInfo),
	              string.Format(
	              	"Action {0} of {1} has parameter of type {2} which is invalid. Use only true pocos from the view models assembly.",
	              	methodInfo.Name,
	              	methodInfo.DeclaringType.Name,
	              	parameterInfo.ParameterType.Name));
}
 
bool IsFormType(ParameterInfo info)
{
	var type = info.ParameterType;
 
	return
		type.Assembly == Assembly.GetAssembly(typeof (LogInForm))
		&& type.Name.EndsWith("Form");
}
 
bool IsPrimitiveType(ParameterInfo info)
{
	// add mores types here if they should be allowed as well
	return
		new List<Type>
			{
				typeof (int),
				typeof (string),
				typeof(int?),
				typeof(Guid)
			}.Contains(info.ParameterType);
}
 
bool ThatIsConcreteController(Type type)
{
	return typeof(Controller).IsAssignableFrom(type) && !type.IsAbstract;
}

That is, I am running through all controller types, getting all actions, and checking the the parameter types are either in the array of accepted types (IsPrimitiveType) or a “true poco” (which in this application is a simple view model whose name ends with “Form” and comes from the right assembly).

This way, I will always know which types are used to deserialize forms. Great! But what about that pesky MissingMethodException whenever I forget to provide a public default contructor in my form models? Easy as cake! That part is checked by the following test:

[Test]
public void AllFormModelsHavePublicDefaultConstructor()
{
	var assembly = Assembly.GetAssembly(typeof (LogInForm));
	var types = assembly.GetTypes().ToList();
 
	types
		.FindAll(ThatCouldBePoco)
		.FindAll(ThatSatisfiesPocoNamingConvention)
		.ForEach(AssertHasPublicDefaultConstructor);
}
 
bool ThatCouldBePoco(Type type)
{
	return type.IsClass
	       && !type.IsAbstract;
}
 
bool ThatSatisfiesPocoNamingConvention(Type type)
{
	var name = type.Name;
 
	return name.EndsWith("Form");
}
 
void AssertHasPublicDefaultConstructor(Type type)
{
	var constructors = type.GetConstructors().ToList();
 
	Assert.IsTrue(constructors.Exists(IsPublicDefaultConstructor),
	              string.Format("Poco type {0} does not provide a public default constructor.",
	                            type.Name));
}
 
bool IsPublicDefaultConstructor(ConstructorInfo info)
{
	var parameters = info.GetParameters();
 
	return parameters.Length == 0;
}

These two tests combined, will assert that nothing will go wrong when submitting forms in my ASP.NET MVC project. That’s just nifty! And I really like the notion that I am helping future me.

Comments are closed.