MSTest v2: Customize test execution
This post is part of the series 'MSTest v2'. Be sure to check out the rest of the blog posts of the series!
- MSTest v2: Setup a test project and run tests
- MSTest v2: Exploring asserts
- MSTest v2: Data tests
- MSTest v2: Test lifecycle attributes
- MSTest v2: Create new asserts
- MSTest v2: Customize test execution (this post)
- MSTest v2: Execute tests in parallel
- MSTest v2: Testing against multiple frameworks
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 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. It allows customizing the way tests are discovered in the class. The TestClass
attribute allows customizing the way tests are discovered. For instance, if you want all the tests 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 shown 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. This extension point helps to factorize similar behaviors!
Do you have a question or a suggestion about this post? Contact me!