As a developer, I understand how time-consuming it is to write a unit test case. Especially if your technical manager has set an expectation of more than 85 percent code coverage.

The Xunit theory attribute provided in the XUNIT framework can be used to pass the dynamic parameters to the unit test cases. which can save a lot of time and make the test cases cleaner.

If you want to cover more than 85% of the code, you’ll need to write n test cases to cover all of the validations and scenarios.

If we go with the tedious approach of writing one test case per scenario, you will end up writing hundreds of test cases with just minor changes in the parameters and all copy-pasted.

XUnit Theory With Memberdata & Inlinedata | Improve Productivity | 2022 1
Identical objects

Let’s write some pieces of code to demonstrate the use of the theory attribute in the XUNIT framework.

namespace DotNetDemo;
public class Employee
{
    public string GetFullName(string firstName, string secondName)
    {
        ArgumentNullException.ThrowIfNull(firstName);
        ArgumentNullException.ThrowIfNull(secondName);
        return $"{firstName} {secondName}";
    }
}

How many unit test cases can you come up with for the GetFullName method in the Employee class mentioned above? Please pause a moment, then continue reading.

xunit vs memberdata
Image Source: https://tenor.com/

  1. Employee_GetFullName_When_FirstString_IsNull_Throw_ArgumentNullException
  2. Employee_GetFullName_When_FirstString_IsEmpty_Throw_ArgumentNullException
  3. Employee_GetFullName_When_FirstString_HasWhiteSpaces_Throw_ArgumentNullException
  4. Employee_GetFullName_When_SecondString_IsNull_Throw_ArgumentNullException
  5. Employee_GetFullName_When_SecondString_IsEmpty_Throw_ArgumentNullException
  6. Employee_GetFullName_When_SecondString_HasWhiteSpaces_Throw_ArgumentNullException
  7. Employee_GetFullName_Should_Have_Space_Between_FirstName_And_LastName
  8. Employee_GetFullName_ShouldNot_Have_Space_After_LastName
  9. Employee_GetFullName_ShouldNot_Have_Space_Before_FirstName

So, in total, nine test cases that I could come up with for the getfullname method in the employee class. If we follow the typical approach of writing the test cases, then we must have totally nice test cases.

Fact Attribute in XUnit

Let’s Write all the above test cases using the XUNIT framework and execute them. Every unit testing framework has its own set of attributes to decorate the methods and classes.

Using the Fact attribute in the Xunit you can mark the methods which have no parameter as test cases that will be executed by the unit test case runner.

If you try to pass a parameter into the Fact, You will get a compile-time warning like this.

XUnit Theory With Memberdata & Inlinedata | Improve Productivity | 2022 2
Fact Method cannot have parameters in XUnit

Let’s see what all the test cases look like after implementation.

namespace DotNetDemo.UnitTests;
public class EmployeeTests
{
    public Employee UnitUnderTest = new Employee();
    [Fact]
    public void Employee_GetFullName_When_FirstString_IsNull_Throw_ArgumentNullException()
    {
        var Act = () => UnitUnderTest.GetFullName(null, "K");
        Assert.Throws<ArgumentNullException>(Act);
    }
    [Fact]
    public void Employee_GetFullName_When_FirstString_IsEmpty_Throw_ArgumentNullException()
    {
        var Act = () => UnitUnderTest.GetFullName("", "K");
        Assert.Throws<ArgumentNullException>(Act);
    }
    
    [Fact]
    public void Employee_GetFullName_When_FirstString_HasWhiteSpaces_Throw_ArgumentNullException()
    {
        var Act = () => UnitUnderTest.GetFullName("    ", "K");
        Assert.Throws<ArgumentNullException>(Act);
    }
    
    [Fact]
    public void Employee_GetFullName_When_SecondString_IsNull_Throw_ArgumentNullException()
    {
        var Act = () => UnitUnderTest.GetFullName("D", null);
        Assert.Throws<ArgumentNullException>(Act);
    }
    
    [Fact]
    public void Employee_GetFullName_When_SecondString_IsEmpty_Throw_ArgumentNullException()
    {
        var Act = () => UnitUnderTest.GetFullName("D", "");
        Assert.Throws<ArgumentNullException>(Act);
    }
    
    [Fact]
    public void Employee_GetFullName_When_SecondString_HasWhiteSpaces_Throw_ArgumentNullException()
    {
          var Act = () => UnitUnderTest.GetFullName("D", "  ");
                Assert.Throws<ArgumentNullException>(Act);
    }
    [Fact]
    public void Employee_GetFullName_Should_Have_Space_Between_FirstName_And_LastName()
    {
        var fullName = UnitUnderTest.GetFullName("Deependra", "Kush");
        var nameArray = fullName.Split(" ");
        
        Assert.NotEmpty(nameArray);
        Assert.Equal(2, nameArray.Length);
        Assert.Equal("Deependra",nameArray[0]);
        Assert.Equal("Kush",nameArray[1]);
    }
    [Fact]
    public void Employee_GetFullName_ShouldNot_Have_Space_Before_FirstName()
    {
        var fullName = UnitUnderTest.GetFullName("Deependra", "Kush");
        var nameArray = fullName.Split(" ");
        
        Assert.NotEmpty(nameArray);
        Assert.Equal(2, nameArray.Length);
        Assert.Equal("Deependra",nameArray[0]);
    }
    
    [Fact]
    public void Employee_GetFullName_ShouldNot_Have_Space_After_LastName()
    {
        var fullName = UnitUnderTest.GetFullName("  Deependra  ", "   Kush  ");
        var nameArray = fullName.Split(" ");
        
        Assert.NotEmpty(nameArray);
        Assert.Equal(2, nameArray.Length);
        Assert.Equal("Kush",nameArray[1]);
    }
  
}

After a successful run of the above test cases.

XUnit Theory With Memberdata & Inlinedata | Improve Productivity | 2022 3

XUnit Theory vs Fact Attribute

As we have seen in the above example, it’s not possible to pass the parameters to the unit test case using the Fact attribute.

We must use the XUnit theory attribute in order to pass the parameters to the test cases.

Both the Attributes [Fact] and [Theory] are defined by xUnit.net.

The xUnit.net test runner uses the [Fact] attribute to distinguish between a “normal” unit test and a test method that doesn’t accept method arguments.

On the other hand, the Theory attribute anticipates one or more DataAttribute instances to provide the values for the method arguments of a Parameterized Test.

There are multiple ways of passing an argument to the theory methods.

  • InlineData
  • MemberData

XUnit InlineData Attribute

XUnit InlineData attribute can be used along with the theory attribute to pass simple parameters to the test case.

It takes the same number of parameters as expected in the unit test case. eg: in the below unit test case takes one parameter & Inline data also has one parameter.

    [Theory]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData(null)]
    public void Employee_GetFullName_Throw_ArgumentNullException_When_FirstName_Is(string firstName)
    {
        var Act = () => UnitUnderTest.GetFullName(firstName, "K");
        Assert.Throws<ArgumentNullException>(Act);
    }

After executing the above test case we will be covering the same number of test cases that we did earlier using the Fact Attribute.

However, we have reduced the redundant code and time taken to implement the multiple test case with some minor differences.

XUnit Theory With Memberdata & Inlinedata | Improve Productivity | 2022 4

Xunit Memberdata

Xunit Memberdata attribute can be used for loading the complex data for the test cases. Any static property or method can be assigned to the XUnit memberdata attribute.

public static IEnumerable<object[]> NamesData =>
    new List<object[]>
    {
        new object[] { "" },
        new object[] { " " },
        new object[] { null },
    };
[Theory]
[MemberData(nameof(NamesData))]
public void Employee_GetFullName_Throw_ArgumentNullException_When_FirstName_Is(string firstName)
{
    var Act = () => UnitUnderTest.GetFullName(firstName, "K");
    Assert.Throws<ArgumentNullException>(Act);
}

As shown in the previous example, NamesData is a static property that can be used inside the xunit memberdata attribute to provide parameter values during test case execution.

Conclusion

Using the XUnit Theory attribute can be utilized to improve the complete development effort by reducing the number of test cases we have to write and also it makes the code cleaner and readable.

85 / 100

4 Comments

Comments are closed