Testing your C# code with xUnit
Automating code testing is crucial to be confident that your code works properly. In this article well learn how to implement unit tests in C# with xUnit.
Table Of Contents
- Introducing xUnit
- Creating Facts
- Using theories
- TDD, BDD, and xUnit
- xUnit and other .NET testing frameworks
It should be clear to everyone, by now, that automating code testing is not a whim, a habit of some programmers, nor an optional activity. It is a necessity that helps you in being confident that your code does what it should and in allowing you to change it without fear.
Usually, although there are many different approaches out there, when talking about testing code most of us just think of unit tests, which is the type of tests that take into account an isolated portion of code to verify its functionalities.
Of course, an application may have different aspects that may be automatically verified, such as the interaction between different components or subsystems, and even the user interaction with the UI. All these aspects are tackled with specific types of tests, like integration tests, UI tests, and end-to-end tests (e2e tests).
While unit tests verify the behavior of an autonomous portion of code, integration tests check the behavior of a combination of code units built with multiple and dependent components. Sometimes those components may involve a database or a file system and so on.
On the other hand, e2e tests are particular integration tests that verify the behavior of complete user functionality.
However, despite the variety of possible test types, unit tests are the basics of automated tests. Every developer should know how to create them within the ecosystem of their programming language.
Let’s take a look, in this article, at how you can create unit tests for your C# code using xUnit, one of the most popular test frameworks for the .NET ecosystem. This open-source library is part of the .NET Foundation and can be used with any .NET programming language. It is part of the more recent versions of .NET Core, so you don’t need to install anything but the .NET Core SDK.
Please, don’t confuse the xUnit testing library we are going to explore in this article with the set of testing frameworks, which unfortunately has the same name! We will discuss the latter later on.
In order to show the features of xUnit, let’s assume you have a class library with the following code:
This class exposes the public method
IsValidAddress() that validates an email address. Of course, better approaches exist to validate an email, but this is irrelevant for our purpose. We are going to set up some unit tests with xUnit to ensure that this method is correct.
What you need to do is to add a new project to your .NET solution based on the xUnit template. If you are using the .NET CLI, you should simply run the following command:
dotnet new xunit
If you are using Visual Studio, just select the xUnit project template when adding a new project to the current solution.
One of the main reasons for xUnit popularity is its essentiality. In fact, a test suite in xUnit is just a standard class with methods implementing each unit test. Let’s take a look at how you can verify that the IsValidAddress() method is able to do its work. As it should normally be, in the following code we are applying the Arrange, Act, Assert pattern to structure our unit test:
As you can see, we defined the ValidEmail() method and applied the Fact attribute to it.
A fact in xUnit is a sort of assertion about a condition, a declaration that must be true. From the syntactical point of view, it is a method decorated by the Fact attribute and containing an invocation of at least one method of the Assert object. In the example above, we arranged the unit test by creating an instance of the MailManager class and assigning a valid email address to the mailAddress variable. Then, we called the IsValidAddress() method and checked the result by using the Assert.True() method.
In this specific case, the Assert.True() method takes two arguments: a condition to be expected as true and a message to display when that condition is false.
The Assert object provides many other methods to check the result of the Act step in a unit test: Assert.Equal(), Assert.NotEqual(), Asset.Null(), Assert.NotNull(), just to mention the most commonly used ones.
Coming back to our unit test example, it is a good practice to also test negative cases. For example, you should verify if the IsValidAddress() method correctly evaluates a not valid email address. The following is the code that accomplishes this task:
Apart from the different name of the unit test method, you just notice that we used the Assert.False() method here. Actually, we expect that this time the
IsValidAddress() method returns a
To run the tests you can use the test runner in Visual Studio or type the following command in a terminal window:
The unit test examples shown above give you an idea of how simple is writing unit tests in xUnit. However, the test cases covered by the two unit tests are not enough to affirm that the
IsValidAddress() method is doing what you expect. There are many other cases of valid addresses and many other cases of invalid ones. For example, also the string firstname.lastname@example.org is a valid email address, while the empty string is not a valid address.
You may think of creating a unit test for each test case, and it would work. But this is not a practical approach. It has too much repeated code with changes just in the input string.
Luckily, xUnit helps you with theories. A theory is a sort of parametric unit test that allows you to represent a set of unit tests sharing the same structure. In our case, we can leverage theories by writing the following code:
Here you find the
CheckMail() method decorated with a few attributes. The Theory attribute informs the xUnit runner that this is a theory, not a simple fact. The
InlineData attributes define a set of data to be passed to the
CheckMail() method. I fact, unlike the fact-based unit tests, a theory unit test has one or more parameters. In this case, the
CheckMail() method has two parameters: the
mailAddress parameter is the string to be evaluated, while the
expectedTestResult is the result we expect to get by calling the
InlineData attributes allow you to pass a set of input strings with the respective expected results to the
This way, you are able to combine in one unit test a set of specific but similar unit tests.
TDD, BDD, and xUnit
Now you have the basic but practice knowledge about the xUnit testing framework. At this point, you may wonder how xUnit fits with Test Driven Development (TDD) or Behavior Driven Development (BDD). Both are software design practices that emphasize the role of testing as the starting point of the development process.
In particular, TDD promotes test writing before the code to be tested. This practice enables an iterative development cycle where a test is written, it fails, then it is fixed.
BDD is on the same page. It can be considered a branch of TDD since it highlights the need to write the tests before the application code as well. In addition, BDD proposes a human-readable syntax to define tests that act as user requirements.
As you can understand, xUnit doesn’t enforce the practice to use when writing your tests. This means that you can use xUnit with the TDD practice. However, since BDD focuses on a human-centric syntax, it is not easily achieved with the simple syntax of xUnit. To leverage BDD, you should use specific tools, like SpecFlow. This BDD tool lets you define different behavior scenarios by writing tests.
xUnit and other .NET testing frameworks
While xUnit has gained popularity in recent years, other testing frameworks are available in the .NET ecosystem. Beside MSTest, Microsoft’s testing framework provided in bundle with Visual Studio, NUnit is the historical unit testing tool that came into the .NET environment since early 2000. Initially, NUnit was a porting of JUnit, the unit testing framework for Java developers. NUnit, along with JUnit, was part of a set of testing tools having a common architecture and known as xUnit, where x stands for the first letter of the programming language or framework. Unfortunately, this set of tools has the same name as the xUnit library, but they must not be confused with each other.
NUnit is a mature tool, well documented, and widely adopted. It supports parallel execution of unit tests with a class-based isolation level, in contrast to xUnit, which supports a method-based isolation level. Unlike NUnit, xUnit is more modern and intuitive. Its growing popularity is mostly due to its simplicity, expressiveness, and extensibility. The latter feature is very important since it allows you to create your own attributes like Fact and Theory.
You may need other types of tools for your tests. For example, when writing your integration tests, you may need to simulate any of your dependencies, such as a third-party library, a complex component, or an external system. In these cases, using the actual dependency may add a lot of extra effort both in performance and complexity. The standard solution is to mock the dependency, that is, create a light class having the same expected interface of the dependency but a simulated behavior.You can write your own mocking classes and use them in your unit tests, but this leads to more code to write and maintain. Alternatively, you can use a mocking library such as Moq or NSubstitute.
This article provided you with a high-level overview of software testing, while focused on xUnit unit tests. After a quick classification of the different types of tests, you learned how to use facts and theories in order to effectively write your tests using the xUnit library. Of course, xUnit provides you with many other features that let you better organize your tests.
Creating automated tests should be a standard activity for a developer. It should be part of the development task itself, not an optional step. In addition, unit tests are just one type of test you can perform on an application. You learned that other types of test are needed when your application grows, interacts with other components, is part of a complex process, and so on. Each type of test may need its specific tool, but unit tests can be considered the pillars upon which you can build your testing infrastructure.
You can read the orginal version of this article at Codemotion.com, where you will find more related contents. https://www.codemotion.com/magazine/dev-hub/backend-dev/testing-your-c-code-with-xunit/