Back to: ASP.NET Core Web API Tutorials
xUnit Framework Theory Attribute in ASP.NET Core:
In this article, I will discuss how to use xUnit Framework Theory Attribute in ASP.NET Core Web API Application with Examples. Please read our previous article discussing how to Implement Unit Testing in ASP.NET Core Web API Project using xUnit Framework with Examples.
xUnit Framework Theory Attribute in ASP.NET Core:
The Theory attribute in the xUnit testing framework defines data-driven tests. Unlike Fact, which is used for tests that have no parameters and are always executed in the same way, Theory allows us to pass multiple sets of data to a single test method. This is useful for testing methods with various inputs and expected outputs, reducing the need for multiple similar test methods.
To use the Theory attribute, we also need to provide the data sets using InlineData or other data attributes like MemberData or ClassData. We will work with the same example we created in our previous article.
XUnit Framework Theory Attribute with InlineData in ASP.NET Core Web API:
The Theory attribute allows us to pass multiple sets of data to a single test method. Let’s convert the GetUserById_ReturnsCorrectUser and GetUserById_ReturnsNullWhenUserNotFound tests into a single theory test with InlineData. So, modify the UserRepositoryTests as follows:
// Importing the Models namespace which likely contains the UserRepository and User classes using MyAPI.Models; namespace MyAPI.xUnitTests { public class UserRepositoryTests { private readonly UserRepository _userRepository; // Constructor initializes the UserRepository instance public UserRepositoryTests() { _userRepository = new UserRepository(); } // Combined test to verify that GetUserById returns the correct user or null if not found [Theory] [InlineData(1, true)] // User with ID 1 exists [InlineData(99, false)] // User with ID 99 does not exist public void GetUserById_ReturnsExpectedResult(int userId, bool userExists) { // Act var result = _userRepository.GetUserById(userId); // Assert if (userExists) { Assert.NotNull(result); Assert.Equal(userId, result.Id); } else { Assert.Null(result); } } // Test to verify that GetAllUsers returns all users [Fact] public void GetAllUsers_ReturnsAllUsers() { // Act var result = _userRepository.GetAllUsers(); // Assert // Check that result is not null Assert.NotNull(result); // Assuming there are 2 users, check that the count is correct Assert.Equal(2, result.Count()); } // Test to verify that AddUser adds a user correctly [Fact] public void AddUser_AddsUserCorrectly() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Act _userRepository.AddUser(newUser); var result = _userRepository.GetUserById(3); // Assert Assert.NotNull(result); // Check that the user was added and returned Assert.Equal(newUser.Id, result.Id); // Check that the ID is correct Assert.Equal(newUser.Name, result.Name); // Check that the name is correct Assert.Equal(newUser.Email, result.Email); // Check that the email is correct } // Test to verify that UpdateUser updates a user correctly [Fact] public void UpdateUser_UpdatesUserCorrectly() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Act _userRepository.UpdateUser(updatedUser); var result = _userRepository.GetUserById(1); // Assert Assert.NotNull(result); // Check that the user was found Assert.Equal(updatedUser.Name, result.Name); // Check that the name was updated Assert.Equal(updatedUser.Email, result.Email); // Check that the email was updated } // Test to verify that DeleteUser deletes a user correctly [Fact] public void DeleteUser_DeletesUserCorrectly() { // Arrange var userId = 1; // Act _userRepository.DeleteUser(userId); var result = _userRepository.GetUserById(userId); // Assert Assert.Null(result); // Check that the user was deleted and cannot be found } } }
Here,
- [Theory] Attribute Marks the test method as a data-driven test.
- [InlineData(…)] Attribute Provides the input data for the test method. Each set of parameters will be passed to the test method in turn, and the test will be executed once for each set.
In our example, the GetUserById_ReturnsExpectedResult test method is executed twice, once with a valid user ID (1) and once with an invalid user ID (99). This allows for testing different scenarios in a single method, reducing code duplication and improving readability.
XUnit Framework Theory Attribute with MemberData in ASP.NET Core Web API:
MemberData attributes allow us to provide more complex or dynamic test data to our test methods. The MemberData attribute allows you to reference a static property, method, or field that returns an IEnumerable<object[]> containing the test data. So, modify the UserRepository as follows. Here, we add a static property that returns the test data.
// Importing the Models namespace which likely contains the UserRepository and User classes using MyAPI.Models; namespace MyAPI.xUnitTests { public class UserRepositoryTests { private readonly UserRepository _userRepository; // Constructor initializes the UserRepository instance public UserRepositoryTests() { _userRepository = new UserRepository(); } // Combined test to verify that GetUserById returns the correct user or null if not found [Theory] [MemberData(nameof(GetUserByIdTestData))] public void GetUserById_ReturnsExpectedResult(int userId, bool userExists) { // Act var result = _userRepository.GetUserById(userId); // Assert if (userExists) { Assert.NotNull(result); Assert.Equal(userId, result.Id); } else { Assert.Null(result); } } // Test to verify that GetAllUsers returns all users [Fact] public void GetAllUsers_ReturnsAllUsers() { // Act var result = _userRepository.GetAllUsers(); // Assert // Check that result is not null Assert.NotNull(result); // Assuming there are 2 users, check that the count is correct Assert.Equal(2, result.Count()); } // Test to verify that AddUser adds a user correctly [Fact] public void AddUser_AddsUserCorrectly() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Act _userRepository.AddUser(newUser); var result = _userRepository.GetUserById(3); // Assert Assert.NotNull(result); // Check that the user was added and returned Assert.Equal(newUser.Id, result.Id); // Check that the ID is correct Assert.Equal(newUser.Name, result.Name); // Check that the name is correct Assert.Equal(newUser.Email, result.Email); // Check that the email is correct } // Test to verify that UpdateUser updates a user correctly [Fact] public void UpdateUser_UpdatesUserCorrectly() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Act _userRepository.UpdateUser(updatedUser); var result = _userRepository.GetUserById(1); // Assert Assert.NotNull(result); // Check that the user was found Assert.Equal(updatedUser.Name, result.Name); // Check that the name was updated Assert.Equal(updatedUser.Email, result.Email); // Check that the email was updated } // Test to verify that DeleteUser deletes a user correctly [Fact] public void DeleteUser_DeletesUserCorrectly() { // Arrange var userId = 1; // Act _userRepository.DeleteUser(userId); var result = _userRepository.GetUserById(userId); // Assert Assert.Null(result); // Check that the user was deleted and cannot be found } // Define the test data as a static property public static IEnumerable<object[]> GetUserByIdTestData => new List<object[]> { new object[] { 1, true }, new object[] { 99, false } }; } }
Here, the MemberData attribute references a static property (GetUserByIdTestData) that returns the test data. Each item in the list is an array of objects representing the parameters to pass to the test method. Now, run the test, and you should see the expected result.
xUnit Framework Theory Attribute Using ClassData in ASP.NET Core Web API
The ClassData attribute allows us to reference a class that implements IEnumerable<object[]>, which provides more flexibility and can encapsulate more complex data preparation logic. Create a separate class that implements IEnumerable<object[]>. So, create a class file named GetUserByIdTestData within the Test Project and then copy and paste the following code:
// Importing the Collections namespace for using IEnumerable using System.Collections; namespace MyAPI.xUnitTests { // Defining a class GetUserByIdTestData that implements IEnumerable<object[]> public class GetUserByIdTestData : IEnumerable<object[]> { // Implementing the GetEnumerator method to provide test data // This method returns an enumerator that iterates through a collection of object[] arrays. // It is required by the IEnumerable<object[]> interface. public IEnumerator<object[]> GetEnumerator() { // Using yield return to provide test cases // Each test case is represented as an object array with the expected parameters and result yield return new object[] { 1, true }; // Test case with userId 1 and expected result true yield return new object[] { 99, false }; // Test case with userId 99 and expected result false } // This is an explicit implementation of the non-generic IEnumerable.GetEnumerator method. // It simply calls the generic GetEnumerator method. // Explicit interface implementation is used here to provide an implementation for the non-generic IEnumerable interface, // which is also required. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }
The purpose of the above class is to provide a collection of test data that can be used in parameterized unit tests in xUnit. Each element in the collection is an array of objects (object[]) representing the test case parameters. Next, modify the User Repository Test class of the Test Project as follows:
using MyAPI.Models; namespace MyAPI.xUnitTests { public class UserRepositoryTests { private readonly UserRepository _userRepository; // Constructor initializes the UserRepository instance public UserRepositoryTests() { _userRepository = new UserRepository(); } // Combined test to verify that GetUserById returns the correct user or null if not found [Theory] [ClassData(typeof(GetUserByIdTestData))] public void GetUserById_ReturnsExpectedResult(int userId, bool userExists) { // Act var result = _userRepository.GetUserById(userId); // Assert if (userExists) { Assert.NotNull(result); Assert.Equal(userId, result.Id); } else { Assert.Null(result); } } // Test to verify that GetAllUsers returns all users [Fact] public void GetAllUsers_ReturnsAllUsers() { // Act var result = _userRepository.GetAllUsers(); // Assert // Check that result is not null Assert.NotNull(result); // Assuming there are 2 users, check that the count is correct Assert.Equal(2, result.Count()); } // Test to verify that AddUser adds a user correctly [Fact] public void AddUser_AddsUserCorrectly() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Act _userRepository.AddUser(newUser); var result = _userRepository.GetUserById(3); // Assert Assert.NotNull(result); // Check that the user was added and returned Assert.Equal(newUser.Id, result.Id); // Check that the ID is correct Assert.Equal(newUser.Name, result.Name); // Check that the name is correct Assert.Equal(newUser.Email, result.Email); // Check that the email is correct } // Test to verify that UpdateUser updates a user correctly [Fact] public void UpdateUser_UpdatesUserCorrectly() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Act _userRepository.UpdateUser(updatedUser); var result = _userRepository.GetUserById(1); // Assert Assert.NotNull(result); // Check that the user was found Assert.Equal(updatedUser.Name, result.Name); // Check that the name was updated Assert.Equal(updatedUser.Email, result.Email); // Check that the email was updated } // Test to verify that DeleteUser deletes a user correctly [Fact] public void DeleteUser_DeletesUserCorrectly() { // Arrange var userId = 1; // Act _userRepository.DeleteUser(userId); var result = _userRepository.GetUserById(userId); // Assert Assert.Null(result); // Check that the user was deleted and cannot be found } } }
Here, the ClassData attribute references a class (GetUserByIdTestData) that implements IEnumerable<object[]>. This class provides an enumerator that yields the test data. Now, run the test, and you should see the expected result.
In the next article, I will discuss how to Implement Asynchronous Tests using the xUnit Framework in an ASP.NET Core Application with Examples. In this article, I explain how to Use Theory Attribute with the xUnit Framework in an ASP.NET Core Web API Application with Examples. I hope you enjoy this article.