Practical ASP.NET

Beyond Unit Tests with ConventionTests

Be triple sure your code works by augmenting your test platform. This nifty open source library will do that.

The compiler will check that the source code we write is valid, and because of that some say that compilation is the first test of our code. Next, we can write automated unit tests to check that our code operates as expected and produces the correct expected result. And then there's integration and automated user interface testing that helps us to expand our range of automated testing.

That's where ConventionTests comes in. ConventionTests is an open source library that allows us to augment our existing tests and check that the code we write meets certain conventions, hence the name.

An example of a convention tests could be checking that all classes with a certain naming convention are always defined in the correct namespace. This means if a new class is added in the wrong namespace, we'll find out about it sooner and this helps to keep the codebase consistent.

The ConventionTests library is part of the TestStack suite of tools and is installed using the NuGet package TestStack.ConventionTests.

Supplied Conventions Out of the Box
ConventionTests comes with a number of supplied conventions to check your code against, including conventions to check that:

  • All classes have a default constructor.
  • All methods must be declared as virtual.
  • Classes should exist in specified namespaces.
  • Specific file types in a Visual Studio project are set as "embedded resources."
  • ASP.NET MVC controller classes are named using the [controller name]Controller naming convention.
  • Projects do not reference .dlls from bin or obj folders.

It is also possible to define your own custom conventions to check against your source code.

Basic Steps When Using a Convention
There are a few basic steps to go through when creating a convention-based test.

Assuming you have a test project in your solution that references your production code and you have created a new test class/method in your test project:

  1. In this new test method, the first phase is to get a list of all the types in the production code that you want to check against a convention.
  2. Next, choose one of the out of the box conventions (or your own custom convention) and create an instance of the convention class..
  3. Execute the convention test against the list of types from phase 1.

Using the MvcControllerNameAndBaseClassConvention
This convention allows us to test that all our controllers in our ASP.NET MVC application are defined correctly (inherit the correct base class and end with "Convention").

Figure 1 shows the Visual Studio solution containing the MyWebApplication MVC project and the MyWebApplication.Tests tests project.

Visual Studio Solution
[Click on image for larger view.] Figure 1. Visual Studio Solution

The code in Listing 1 shows a test (in this case, using the xUnit.net testing framework) that uses the MvcControllerNameAndBaseClassConvention. This follows the same three steps as mentioned previously..

Listing 1: Test Using the MvcControllerNameAndBaseClassConvention

using TestStack.ConventionTests;
using TestStack.ConventionTests.ConventionData;
using TestStack.ConventionTests.Conventions;
using Xunit;

namespace MyWebApplication.Tests
{
    public class MyTestClass
    {
        [Fact]
        public void AllControllersShouldMeetConventions()
        {
            // Step 1: get a "list" of all the types (e.g. classes) that we want to check against a given convention
            var typesToCheck = Types.InAssemblyOf<MyWebApplication.SomeClassInTheProductionProject>();

            // Step 2: Choose the convention to check the selected types against
            var convention = new MvcControllerNameAndBaseClassConvention();

            // Step 3: Perform the check and fail the test if a convention is broken
            Convention.Is(convention, typesToCheck);
        }
    }
}

Running this test will pass.

If we change the name of the home controller to just "Home" (and not obey the convention of suffixing it with "Controller"), the test will now fail, as we can see in Figure 2.

A Failing Convention Test
[Click on image for larger view.] Figure 2. A Failing Convention Test

If we check the test output (Figure 3), we can see the offending controller is MyWebApplication.Controllers.Home.

Test Output for Failing Test
[Click on image for larger view.] Figure 3. Test Output for Failing Test

Using the ClassTypeHasSpecificNamespace
This convention allows us to check the right types of classes are being declared in the right place/namespace.

The code in Listing 2 shows a test using this convention to ensure all view model classes (classes whose names end with "ViewModel") are defined in the MyWpfApplication.ViewModel namespace. This time we're using NUnit as our testing framework.

Listing 2: Using the ClassTypeHasSpecificNamespace Convention

using NUnit.Framework;
using TestStack.ConventionTests;
using TestStack.ConventionTests.ConventionData;
using TestStack.ConventionTests.Conventions;
namespace MyWpfApplication.Tests
{
    [TestFixture]
    public class MyTestClass
    {
        [Test]
        public void AllViewModelsShouldBeInCorrectNamespace()
        {
            var typesToCheck = Types.InAssemblyOf<MyWpfApplication.MainWindow>();
            var convention = new ClassTypeHasSpecificNamespace(x => x.Name.EndsWith("ViewModel"), "MyWpfApplication.ViewModel", "view model classes used for data binding");
            Convention.Is(convention, typesToCheck);
        }
    }
}

In addition to the pre-supplied conventions, it's also possible to create your own custom conventions. We'll look at this advanced technique in a future article.

About the Author

Jason Roberts is a Microsoft C# MVP with over 15 years experience. He writes a blog at http://dontcodetired.com, has produced numerous Pluralsight courses, and can be found on Twitter as @robertsjason.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.