MSTest v2: Customize test execution

This post is part of the serie 'MSTest v2'. Be sure to check out the rest of the blog posts of the serie!

MSTest v2 is extensible. In the previous posts, we saw how to extend DataTest and create new assert methods. In this post, we'll see how to customize the way the tests are executed.

Customizing execution at test method level

By default, the runner will execute the methods decorated by [TestMethod]. If you look closer at the TestMethod attribute, you'll see that the class contains the logic of the test execution. Indeed, the method Execute (GitHub) call the test method and return the result of the execution.

public class TestMethodAttribute : Attribute
{
    public virtual TestResult[] Execute(ITestMethod testMethod)
    {
        return new TestResult[] { testMethod.Invoke(null) };
    }
}

You can see the method is virtual. So, you can extend this class to customize the way the method is called. For instance, if you need to execute the method in an STA thread, you can create your own attribute and override the Execute method to use an STA thread.

public class STATestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
            return Invoke(testMethod);

        TestResult[] result = null;
        var thread = new Thread(() => result = Invoke(testMethod));
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
        return result;
    }

    private TestResult[] Invoke(ITestMethod testMethod)
    {
        return new[] { testMethod.Invoke(null) };
    }
}

You can use this attribute instead of TestMethod:

[TestClass]
public class TestClass1
{
    [STATestMethod]
    public void Test_STA()
    {
        Assert.AreEqual(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    }
}

Customizing execution at test class level

Some of you may ask, why do you need the TestClass attribute as each test is already decorated by TestMethod. It seems redundant. Indeed, the test runner may discover tests by only using the TestMethod attribute. In fact, it allows to customize the way test are discovered in the class. The TestClass attribute allows to customize the way tests are discovered. For instance, if you want all the test of a class to be executed in an STA thread, you can create a class that inherits from TestClass.

public class STATestClassAttribute : TestClassAttribute
{
    public override TestMethodAttribute GetTestMethodAttribute(TestMethodAttribute testMethodAttribute)
    {
        if (testMethodAttribute is STATestMethodAttribute)
            return testMethodAttribute;

        return new STATestMethodAttribute(base.GetTestMethodAttribute(testMethodAttribute));
    }
}

public class STATestMethodAttribute : TestMethodAttribute
{
    private readonly TestMethodAttribute _testMethodAttribute;

    public STATestMethodAttribute()
    {
    }

    public STATestMethodAttribute(TestMethodAttribute testMethodAttribute)
    {
        _testMethodAttribute = testMethodAttribute;
    }

    public override TestResult[] Execute(ITestMethod testMethod)
    {
        // code omitted for brevity (same as above)
    }

    private TestResult[] Invoke(ITestMethod testMethod)
    {
        if (_testMethodAttribute != null)
            return _testMethodAttribute.Execute(testMethod);

        return new[] { testMethod.Invoke(null) };
    }
}

You can use this attribute instead of TestClass:

[STATestClass]
public class TestClass1
{
    [TestMethod]
    public void Test1()
    {
        Assert.AreEqual(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    }

    [STATestMethod]
    public void Test2()
    {
        Assert.AreEqual(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    }

    [DataTestMethod]
    [DataRow(1)]
    [DataRow(2)]
    public void Test3(int i)
    {
        Assert.AreEqual(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    }
}

All methods of the class are executed in an STA thread.

Conclusion

MSTest v2 is easily extensible. In this post, I've show how to use an STA thread to execute tests. But there are many scenarios, such as changing the culture of the current thread or executing tests many times to ensure the stability of a test. Indeed, you can create a Culture attribute to set the culture, or a RepeatTestMethod attribute to execute the test N times or for a specific period of time. This extension point really helps to factorize similar behaviors!