xUnit Theory is a great way of writing data-driven tests. It provides a simple and easy way to write repetitive tests through attributes such as InlineData, MemberData, and ClassData.
For this post, I have assumed that you are already aware of xUnit Theory and I’m going talk to a little bit more about how we can write better descriptive tests using MemberData.
Let us consider a simple example. We have a class called AnimalRepository with a method Find.
The above code is self-explanatory. As you can see, the Find method allows a user to search for an animal name from the in-memory list of animals. Let’s say, we now need to unit test the Find method of class AnimalRepository.
The code below shows how a typical test for the Find method would like using XUnit theory.
As you can see the xUnit Theory provides a simple consistent way to create a single test with different test data sources.
The above code, however, has some drawbacks when compared to a xUnit Fact. Here are some of the cons:
- Unlike Fact where we have different tests each scenario, the tests here are no longer descriptive. In the above approach, it is not always possible for a new developer to easily understand what scenario we are trying to test.
- On running the tests through Visual Studio Test Explorer or ReSharper or even dotnet test command, the test output is not so easy to understand.
- If we have a huge test dataset, then it is difficult to relate the test data with the corresponding test output especially when the test fails for one or more test data.
We first start with an abstract class TestSource.
As you can see in the code above, the TestSource constructor takes the testName as input parameter and overrides the ToString() method to return TestName.
The test output rendered by xUnit Theory can be updated by overriding the ToString() method on the data object.
Next, we update our AnimalRespositoryTests class as below.
Few things to highlight in above code:
- We create a class AnimalRepositoryTestSource which inherits from TestSource. Typically this class would remain in the same file as AnimalRepositoryTests.
- AnimalRepositoryTestSource constructor takes the parameters required for our test data in Version 1 along with the CallerMethodName which gives us the method name of “caller to the method”. I have talked more about CallerMethodName in one of my previous posts.
- Test data in GetFindAnimalData method is divided into separate methods. Each method name clearly describes what we are trying to test.
- The methods create an instance of AnimalRepositoryTestSource by passing the relevant test data as parameters in its constructor.
- FindReturnsCorrectResult is updated to accept AnimalRepositoryTestSource object.
That’s it! With the above code changes, our test output would look much simpler and more descriptive.
I hope you find this tip useful for your tests written with xUnit Theory. 🙂
The source code of the code samples in this blog is available on GitHub here.