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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
[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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
[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.