Convention over Configuration is a great way to cut down repetitive boilerplate code. But how do you validate that your code adheres to your conventions? Convention Tests is a code-only NuGet that provides a simple API to build validation rules for convention validation tests.
ConventionTests is a simple code-only Nuget that provides a minimalistic and limited API enforcing certain structure when writing convention tests and integrating with NUnit. Installing it will add two .cs files to the project and a few dependencies (NUnit, Castle Windsor and ApprovalTests).
ConventionTests.NUnit file is where all the relevant code is located and __Run file is the file that runs your tests. The approach is to create a file per convention and name them in a descriptive manner, so that you can learn what the conventions you have in the project are by just looking at the files in your Conventions folder, without having to open them.
Each convention test inherits (directly or indirectly) from the IConventionTest interface. There’s an abstract implementation of the interface, ConventionTestBase and a few specialized implementations for common scenarios provided out of the box: Type-based one (ConventionTest) and two for Windsor (WindsorConventionTest, non-generic and generic for diagnostics-based tests).
The most common and most generic group of conventions are ones based around types and type information. Conventions like “every controller’s name ends with ‘Controller’”, or “Every method on WCF service contracts must have OperationContractAttribute” are examples of such conventions.
You write them by creating a class inheriting ConventionTest, which forces you to override one method. Here’s a minimal example
public class Controllers_have_Controller_suffix_in_type_name : ConventionTest
{
protected override ConventionData SetUp()
{
return new ConventionData
{
Types = t => t.IsConcrete<IController>(),
Must = t => t.Name.EndsWith("Controller")
};
}
}
Another common set of convention tests are tests regarding an IoC container. Castle Windsor is supported out of the box. The structure of the tests and API is similar, with the difference being that instead of types we’re dealing with Windsor’s component Handlers.
public class List_classes_registered_in_Windsor : WindsorConventionTest
{
protected override WindsorConventionData SetUp()
{
return new WindsorConventionData(new WindsorContainer()
.Install(FromAssembly.Containing<AuditedAction>()))
{
FailDescription = "All Windsor components",
FailItemDescription = h => BuildDetailedHandlerDescription(h)+" | "+
h.ComponentModel.GetLifestyleDescription(),
}.WithApprovedExceptions("We just list all of them.");
}
}
Say we wanted to create a convention test that lists all of our NHibernate collections where we do cascade deletes, so that when we add a new collection the test would fail reminding us of the issue, and force us to pay attention to how we structure relationships in the application. To do this we could create a base NHibernateConventionTest and NHiibernateConventionData to create similar structure, or just build a simple one-class convention like that:
public class List_collection_that_cascade_deletes:ConventionTestBase
{
public override void Execute()
{
// NH Bootstrapper is our custom class to set up NH
var bootstrapper = new NHibernateBootstrapper();
var configuration = bootstrapper.BuildConfiguration();
var message = new StringBuilder("Collections with cascade delete orphan");
foreach (var @class in configuration.ClassMappings)
{
foreach (var property in @class.PropertyIterator)
{
if(property.CascadeStyle.HasOrphanDelete)
{
message.AppendLine(@class.NodeName + "." + property.Name);
}
}
}
Approve(message.ToString());
}
}
Krzysztof Koźmic spoke about ConventionTests at NDC 2012. You can find the video of that talk here, slides here and the introductory blog post here.