Using Inline data and Theory – Data Driven Testing in C#.NET
Today in this article we will see how to leverage using Inline data and Theory – Data Driven Testing in C#.NET mainly using the XUnit test framework.
We will use data-driven concepts for writing better tests saving time, and cost, and getting improved results in application development.
The data-driven concept can be leveraged for Unit Testing, Integration Testing or any Functional Testing (ATCD or BDD) approaches you are following.
They are very helpful means for Test Data management(TDM).
I shall be using XUnit as a testing framework for today’s discussion. However same concepts should be easily available in any of your other testing frameworks like MSTest or NUnit etc.
We can easily set up a test method to retrieve values from different sources like through Attributes or using Data sources like files (Example – .JSON file, XML, XLSX), DataBases (like SQL, Mongo, MSAccess).
The same test method can be used successively for different data items, which makes it easy to test a variety of test data using a single method.
Getting Started
I have a simple Service with a few methods which I would like to test using a Data-driven approach.
Below is our Unit Of Work i.e We will be testing the method GetLoanStatus () from class EmployeeLoanDetails
public class EmployeeLoanDetails : IEmployeeLoan
{
public string Staus { get; set; }
public string GetLoanStatus(int creditScore)
{
var status = string.Empty;
try
{
if (creditScore < 550 && creditScore > 0)
status = "Declined";
else if (creditScore <= 675 && creditScore > 550)
status = "Review";
else if (creditScore >= 675 && creditScore < 1000)
status = "Approved";
else if (creditScore == 2000)
throw new InvalidCastException();
else if (creditScore < 0)
status = "Invalid input";
else
status = "Invalid input";
}
catch (Exception)
{
throw;
}
return status;
}
}
Write your first Theory
We have seen how to use Facts Tests. The theory is essentially a test with particular data sources.
There are multiple ways to provide the data source for a test method.
XUnit -InlineData for Unit Test Cases
This attribute provides data sources from inline values.
[Theory]
[InlineData(445, "Declined")]
[InlineData(750, "Approved")]
[InlineData(650, "Review")]
public void EmployeeLoanDetails_GetCreditstatus_CreditScoreDataDriven(int Credit, string expected)
{
EmployeeLoanDetails details = new EmployeeLoanDetails();
string actual = details.GetLoanStatus(Credit);
Assert.Equal(expected, actual);
}
Please note the below points while using the InlineData attribute,
- Attribute your Test method with [Theory] before using InlineData.
- A test method will run for a given number of InlineData provided. For example: Above the test, the method will run 3 times.
- InlineData provided data including the number of the arguments and their types should strictly match with a number of arguments and their type provided through TestMethod().
The above test cases will run 3 times as there are 3 inline data provided.
XUnit – DataMember for Unit Test Cases
This attribute provides data sources from the below sources.
- Static Property
- Static Fields
- Static Methods
In all the above ways the members should return something which is compatible with IEnumerable<object[]>.
Above the same method now can be written as below,
[Theory]
[MemberData(nameof(TestDataMember))]
public void
EmployeeLoanDetails_GetCreditStatus_CreditScoreDataDriven1(int
Credit, string expected)
{
EmployeeLoanDetails details = new EmployeeLoanDetails();
string actual =details.GetLoanStatus(Credit);
Assert.Equal(expected, actual);
}
And data source can be centralized as below,
public static IEnumerable<object[]> TestDataMember()
{
yield return new object[] { 445, "Declined" };
yield return new object[] { 650, "Review" };
yield return new object[] { 750, "Approved" };
yield return new object[] { 0, "Invalid input" };
}
XUnit – DataClass for Unit Test Cases
This attribute provides data sources from the Class sources. The class should return something which is compatible with IEnumerable<object[]>.
Above the same method now can be written as below,
[Theory]
[ClassData(typeof(LoanStatusTestData))]
public void EmployeeLoanDetails_GetCreditStatus_CreditScoreDataDrivenComplexType(LoanStatus stat)
{
EmployeeLoanDetails details = new EmployeeLoanDetails();
string actual = details.GetLoanStatus(stat.CreditScore);
Assert.Equal(stat.Status, actual);
}
Data source through Class LoanStatusTestData is defined as below,
private class LoanStatusTestData : IEnumerable<object[]>
{
private readonly List<LoanStatus[]> _data = new
List<LoanStatus[]>
{
new LoanStatus[] { new LoanStatus {CreditScore = 650,
Status= "Review" } },
new LoanStatus[] { new LoanStatus { CreditScore = 800,
Status = "Approved" } },
new LoanStatus[] { new LoanStatus { CreditScore = 500,
Status = "Declined" } }
};
public IEnumerator<object[]> GetEnumerator()
{
return this._data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
The above test cases will run 3 times as there are 3 objects provided as data sources.
The above pattern can be extended to use Data sources from files or databases easily. I shall talk about the same very soon in my next article.
We shall make use of the same concept of Loading Configuration (.INI , .JSON, .XML) in .NET Core and shall feed the required data to our test cases. So until then stay tuned!!
Summary
Today in this article we learned how to leverage data-driven testing concepts for writing better tests by saving time, and cost and earning improved testing capability.
Tests that have the same structure can be provided with multiple sets of data without a hassle. Data-Driven testing reduces the number of tests in the file without compromising the coverage.
Other useful references,
Do you have any comments or ideas or any better suggestions to share?
Please sound off your comments below.
Happy Coding !!
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
This was very helpful to understand the Xunit and its supported feature. Thank you!
Thanks Adam- Glad the article was helpful.
The pitfall that you didn’t mention is that XUnit does not support running the same Theory in parallel with different inputs.
So if you had hundreds of different inputs for the same method, then XUnit would run it sequentially leading long execution times. This is especially unusable for integration tests.
Hi John, Thanks for putting up those details. I believe we should get around that problem by means if some customization. And also we might see supported feature addressed in future version if it doesn’t exist now..
Data driven is good concept and can be still leveraged finding work around on any known limitation.
No, it will never be supported by the xunit devs, they themselves stated so on the github issue regarding this problem. This isn’t a missing feature, it was a conscious decision on their part to exclude parallelism in this case.
Why they chose to do so, beats me.
The only way around this is to build an extension to xunit using their APIs, but I think this could be a huge task, because it involves extending the core program.
Great concept and very good article !! . Thanks
Thanks Jeff. I appreciate your time and for providing encouraging feedback !. Thank you .. Keep visiting my articles.