In the last post, I briefly described how to automatically migrate your MSTest tests to XUnit by using the XUnitConverter
utility. Of course, nothing is ever that simple; MSTest has some concepts that XUnit expresses very differently1 like how to share code between tests whether that is setup, fixtures, cleanup, or data. Some of these concepts are implemented differently enough that automating the migration from one to the other would be very difficult if not impossible. However, some of it really is that simple. Before we look at the difficult examples, I thought it would be useful to illustrate how some of the simple concepts map from MSTest to XUnit using an example2.
So, let's look at an MSTest example (contrived, of course):
[TestClass] public class MyTests { private MemoryStream _stream; [TestInitialize] public void Initialize() { _stream = new MemoryStream(); } [TestCleanup] public void Cleanup() { _stream.Dispose(); } [TestMethod] public void Test1() { //Arrange //Act //Assert } [TestMethod] public void Test2() { //Arrange //Act //Assert } }
Clearly, I cheated by not actually making the tests do anything, but the content of the test methods is mostly irrelevant; you set some stuff up, you do something, and you assert a result–it's all the same regardless of the test framework. However, this is a simple example of a test class written for the MSTest framework. There are attributes to tell the framework that the class is a test class, which methods inside of it are test methods, and which methods should be called before and after each test. In this case, our test initialization creates a stream, which is then disposed of in the cleanup method; each test method would get sandwiched in the middle.
After converting to XUnit with the converter tool, the same class will look something like this:
public class MyTests : IDisposable { private MemoryStream _stream; public MyTests() { _stream = new MemoryStream(); } public void Dispose() { _stream.Dispose(); } [Fact] public void Test1() { //Arrange //Act //Assert } [Fact] public void Test2() { //Arrange //Act //Assert } }
There are a few things that happened.
- The class no longer has an attribute. XUnit knows the class is a test class because it contains tests3.
- The tests are decorated with a
[Fact]
attribute, which is equivalent to[TestMethod]
. - The
[TestInitialize]
and[TestCleanup]
attributes are gone. Instead, the class constructor is used for test initialization and theDispose
method along with deriving fromIDisposable
indicates that there is test cleanup code.
Overall, I love how the XUnit syntax works with C# syntax and .NET idioms in declaring tests. Not only does this reduce the ceremony around defining tests by reducing the various decorators, but it also allows for cleaner coding practices. For example, we can now correctly mark our memory stream member variable as readonly
.
private MemoryStream _stream;
By relying on C# syntax and standard interfaces in this way, the lifecycle of a test is clearer too; XUnit will construct and dispose the class for each test in the class, making it easy to see how each test will run. This idiomatic way of declaring tests allows for separation of concerns, keeping test classes light and focused. This will be illustrated when we later look at other concepts in MSTest like [ClassInitialize]
and [ClassCleanup]
, TestContext
, and [DeploymentItem]
, and how XUnit tackles the problems these concepts solved.
Regarding your 3rd comment. I can only guess that MSTest (and NUnit) needs the class to be marked as a TestClass/TestFixture as an optimization. Since the test runner uses reflection to find the tests it can reduce that time by not checking every method of every class – instead just the methods in a class marked with a specific attribute.
Also don't underestimate group mentality – this is the way it's usually done throughout many unit testing frameworks (and programming languages)
Great series. I've been struggling on how to implement setup and cleanup after each test. Thanks a lot.
Thanks, Tom! I am really glad this has helped you. Jeff
I guess you didnt actually understand what TestInitialize actually does to thing that a constructor can be a replacement
Thank you for your comment, Paco.
Considering the differences between MSTest and XUnit2, I am certain that the use of a constructor for the case I provided is the XUnit2 equivalent of TestInitialize in MSTest. The XUnit2 documentation even calls out this correlation:
Source: https://xunit.github.io/docs/comparisons
Is it perhaps plausible that you may not understand what a constructor does in XUnit2 such that you do not think it is an appropriate replacement for MSTest's TestInitialize?
What if MSTest's TestInitialize was async?
Is there anything like TestFixtureSetUp from NUnit 2.1?
The TestInitialize/Constructor runs before every test. What if we need to initialize once for all testes? like automapper
Yes, you can definitely do that. Checkout this post on ClassInitialize and sharing data.