CsabaDu.DynamicTestData.xUnit
1.0.0
See the version list below for details.
dotnet add package CsabaDu.DynamicTestData.xUnit --version 1.0.0
NuGet\Install-Package CsabaDu.DynamicTestData.xUnit -Version 1.0.0
<PackageReference Include="CsabaDu.DynamicTestData.xUnit" Version="1.0.0" />
<PackageVersion Include="CsabaDu.DynamicTestData.xUnit" Version="1.0.0" />
<PackageReference Include="CsabaDu.DynamicTestData.xUnit" />
paket add CsabaDu.DynamicTestData.xUnit --version 1.0.0
#r "nuget: CsabaDu.DynamicTestData.xUnit, 1.0.0"
#addin nuget:?package=CsabaDu.DynamicTestData.xUnit&version=1.0.0
#tool nuget:?package=CsabaDu.DynamicTestData.xUnit&version=1.0.0
CsabaDu.DynamicTestData.xUnit
CsabaDu.DynamicTestData.xUnit
is a lightweight, robust type-safe C# library designed to facilitate dynamic data-driven testing in xUnit framework, by providing a simple and intuitive way to generate TheoryData
instances at runtime, based on CsabaDu.DynamicTestData
features.
Table of Contents
- Description
- Features
- Quick Start
- Types
- How it Works
- Usage
- Contributing
- License
- Contact
- FAQ
- Troubleshooting
Description
CsabaDu.DynamicTestData.xUnit
framework provides a set of utilities for dynamically generating and managing test data, particularly in xUnit. It simplifies the process of creating parameterized tests by offering a flexible and extensible way to define test cases with various arguments, expected results, and exceptions, based on CsabaDu.DynamicTestData
features and the TheoryData
type of xUnit.
Features
Inherited CsabaDu.DynamicTestData
Features:
- Complete functionality of the
CsabaDu.DynamicTestData
framework is available as dependency.
TheoryData
Type Support:
- The generic
TestData
record ofCsabaDu.DynamicTestData
framework and its derived types (TestDataReturns
,TestDataThrows
) which support up to nine arguments (T1
toT9
) are used forTheoryData
instances creation at runtime.
Struct
Support:
- The
AddTestDataReturnsToTheoryData
methods are designed for creating test cases that expect returning a struct (value type).
Exception
Support:
- The
TestDataThrows
type which is specifically designed for test cases that expect exceptions to be thrown can either be used to createTheoryData
instances with theAddTestDataThrowsToTheoryData
. - It includes the expected exception type and any arguments required for the test.
DynamicTheoryDataSource
Abstract Class:
- Provides methods (
AddTestDataToTheoryData
,AddTestDataReturnsToTheoryData
,AddTestDataThrowsToTheoryData
) to createTheoryData
of xUnit instances and add the converted test data to it for data-driven test methods. - These methods use the
ArgsCode
enum ofCsabaDu.DynamicTestData
to determine ifTestcaseData
instances shall consist ofTestData
record instances or their properties.
Dynamic Data Generation:
- Designed to easily generate
TheoryData
instances dynamically.
Type Safety:
- Ensures type safety for generated test data with using
TestData
generic types forTheoryData
instances creation.
Thread Safety:
- The generated
TestData
record types' immutability ensures thread safety of tests withTheoryData
types too.
Readability:
- The
TestCase
property of the TestData types is designed to create a literal test description to display in Visual Studio Test Explorer when using asTheoryData
element.
xUnit Integration:
- Easy to integrate with xUnit framework.
- Seamlessly create
TheoryData
instances and add the converted test data to it for use in parameterized tests.
Portability:
- Besides xUnit support and dependency, easy to integrate with other test frameworks as well.
Quick Start
- Install the NuGet package:
- You can install the
CsabaDu.DynamicTestData.xUnit
NuGet package from the NuGet Package Manager Console by running the following command:Install-Package CsabaDu.DynamicTestData.xUnit
- Create a derived dynamic
TestCaseData
source class:
- Create one class for each test class separately that extends the
DynamicTestCaseDataSource
base class. - Implement
TheoryData
returning (base) type methods to generate test data. - Use the
AddTestDataToTheoryData
,AddTestDataReturnsToTheoryData
, andAddTestDataThrowsToTheoryData
methods to add the test data which were dynamically created within the methods to theTheoryData TheoryData
property. - (See the Sample DynamicTheoryDataSource Child Class section for a sample code.)
- Insert the
TheoryData
source instance in the test class:
- Declare a static instance of the derived
DynamicTheoryDataSource
child class in the test class and initiate it with eitherArgsCode.Instance
orArgsCode.Properties
parameter. - Declare static
TheoryData<>
properties or methods with exact type parameters to call the test data generated by the dynamic data source class. - Cast the called
TheoryData
instances to the exact types ofTheoryData<>
to use it in the test methods.
- Use dynamic
TheoryData
source members in the test methods:
- Use the
MemberData
attribute in xUnit to pass the test data to the test methods. - Initialize the attribute with the belonging dynamic data source member name.
- (See the Sample Test Classes with TheoryData source or section for sample codes.)
Types
DynamicTheoryDataSource
Abstract Class
- Purpose: Represents an abstract base class for dynamic
TheoryData
sources. - Property:
TheoryData
: Gets or sets theTheoryData
used for parameterized tests.
- Methods:
AddTestDataToTheoryData<T1, T2, ..., T9>(...)
: Adds test data to theTheoryData
instance with one to nine arguments.AddTestDataReturnsToTheoryData<TStruct, T1, T2, ..., T9>(...)
: Adds test data toTheoryData
instance for tests that expect a struct to assert.AddTestDataThrowsToTheoryData<TException, T1, T2, ..., T9>(...)
: Adds test data toTheoryData
instance for tests that throw exceptions.ResetTheoryData()
: Sets theTheoryData
property with null value.
How it Works
This framework is the extension of CsabaDu.DynamicTestData framework. If you are not familiar with that framework yet, learn more about it, especially about the ArgsCode Enum, the ITestData Base Interfaces and TestData Record Types of that.
Abstract DynamicTheoryDataSource
Class
This class extends the abstract DynamicDataSource
class of CsabaDu.DynamicTestData
framework. (To learn more about the base class, see Abstract DynamicDataSource Class.)
This class contains the methods to add TestData
instances of CsabaDu.DynamicTestData
framework or its propertes to an initiated TheoryData
instance. (To learn more about the TestData
types of CsabaDu.DynamicTestData
, see ITestData Base Interfaces and TestData Record Types.) Once you call an AddTestData...
method of the class, initialize a new TheoryData
instance inside if the TheoryData
property is null, and adds the test data to it.
Parameters of the methods are the same as the object array generator methods of the parent DynamicDataSource
class, as well as the intended usage of it:
- extend this class for each test class separately,
- implement the necessary specific methods in the derived class with the
TheoryData
returning type, and - declare a static instance of the derived class in the test class with the exact generic
TheoryData<>
type where it is going to be used.
You should do two more specific steps:
- Cast the called
TheoryData
returning type method to the exact genericTheoryData<>
type. - Implement the
IDisposable
interface and call theResetTheoryData()
method of the data source class with theDispose()
method call.
namespace CsabaDu.DynamicTestData.xUnit.DynamicDataSources;
public abstract class DynamicTheoryDataSource(ArgsCode argsCode) : DynamicDataSource(argsCode)
{
internal const string ArgumentsAreSuitableForCreating = "Arguments are suitable for creating ";
internal const string ArgsCodePropertyHasInvalidValue = "ArgsCode property has invalid value: ";
internal string ArgumentsMismatchMessageEnd => " elements and do not match with the initiated "
+ TheoryData?.GetType().Name + " instance's type parameters.";
private InvalidOperationException ArgsCodeProperyValueInvalidOperationException
=> new(ArgsCodePropertyHasInvalidValue + (int)ArgsCode);
protected TheoryData? TheoryData { get; set; } = null;
public void ResetTheoryData() => TheoryData = null;
internal string GetArgumentsMismatchMessage<TTheoryData>() where TTheoryData : TheoryData
=> ArgumentsAreSuitableForCreating + typeof(TTheoryData).Name
+ ArgumentsMismatchMessageEnd;
private TTheoryData CheckedTheoryData<TTheoryData>(TTheoryData theoryData) where TTheoryData : TheoryData
=> (TheoryData ??= theoryData) is TTheoryData typedTheoryData ?
typedTheoryData
: throw new ArgumentException(GetArgumentsMismatchMessage<TTheoryData>());
#region AddTestDataToTheoryData
public void AddTestDataToTheoryData<T1>(string definition, string expected, T1? arg1)
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(arg1);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestData<T1?> getTestData() => new(definition, expected, arg1);
static TheoryData<TestData<T1?>> initTestDataTheoryData() => [];
static TheoryData<T1?> initTheoryData() => [];
#endregion
}
public void AddTestDataToTheoryData<T1, T2>(string definition, string expected, T1? arg1, T2? arg2)
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(arg1, arg2);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestData<T1?, T2?> getTestData() => new(definition, expected, arg1, arg2);
static TheoryData<TestData<T1?, T2?>> initTestDataTheoryData() => [];
static TheoryData<T1?, T2?> initTheoryData() => [];
#endregion
}
// AddTestDataToTheoryData<> overloads here...
#endregion
#region AddTestDataReturnsToTheoryData
public void AddTestDataReturnsToTheoryData<TStruct, T1>(string definition, TStruct expected, T1? arg1)
where TStruct : struct
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(expected, arg1);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestDataReturns<TStruct, T1?> getTestData() => new(definition, expected, arg1);
static TheoryData<TestDataReturns<TStruct, T1?>> initTestDataTheoryData() => [];
static TheoryData<TStruct, T1?> initTheoryData() => [];
#endregion
}
public void AddTestDataReturnsToTheoryData<TStruct, T1, T2>(string definition, TStruct expected, T1? arg1, T2? arg2)
where TStruct : struct
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(expected, arg1, arg2);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestDataReturns<TStruct, T1?, T2?> getTestData() => new(definition, expected, arg1, arg2);
static TheoryData<TestDataReturns<TStruct, T1?, T2?>> initTestDataTheoryData() => [];
static TheoryData<TStruct, T1?, T2?> initTheoryData() => [];
#endregion
}
// AddTestDataReturnsToTheoryData<> overloads here...
#endregion
#region AddTestDataThrowsToTheoryData
public void AddTestDataThrowsToTheoryData<TException, T1>(string definition, TException expected, T1? arg1)
where TException : Exception
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(expected, arg1);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestDataThrows<TException, T1?> getTestData() => new(definition, expected, arg1);
static TheoryData<TestDataThrows<TException, T1?>> initTestDataTheoryData() => [];
static TheoryData<TException, T1?> initTheoryData() => [];
#endregion
}
public void AddTestDataThrowsToTheoryData<TException, T1, T2>(string definition, TException expected, T1? arg1, T2? arg2)
where TException : Exception
{
switch (ArgsCode)
{
case ArgsCode.Instance:
CheckedTheoryData(initTestDataTheoryData()).Add(getTestData());
break;
case ArgsCode.Properties:
CheckedTheoryData(initTheoryData()).Add(expected, arg1, arg2);
break;
default:
throw ArgsCodeProperyValueInvalidOperationException;
}
#region Local methods
TestDataThrows<TException, T1?, T2?> getTestData() => new(definition, expected, arg1, arg2);
static TheoryData<TestDataThrows<TException, T1?, T2?>> initTestDataTheoryData() => [];
static TheoryData<TException, T1?, T2?> initTheoryData() => [];
#endregion
}
// AddTestDataThrowsToTheoryData<> overloads here...
#endregion
}
Usage
Here are some basic examples of how to use CsabaDu.DynamicTestData.NUnit
in your project.
Sample DemoClass
The following bool IsOlder(DateTime thisDate, DateTime otherDate)
method of the DemoClass
is going to be the subject of the below sample dynamic data source and test method codes.
The method compares two DateTime
type arguments and returns true
if the first is greater than the second one, otherwise false
. The method throws an ArgumentOutOfRangeException
if either argument is greater than the current date.
This demo class is the same as used in the Sample DemoClass CsabaDu.DynamicTestData
sample codes, to help you compare the implementations of the dynamic data sources and test classes of the different CsabaDu.DynamicTestData
frameworks with each other
namespace CsabaDu.DynamicTestData.SampleCodes;
public class DemoClass
{
public const string GreaterThanCurrentDateTimeMessage
= "The DateTime parameter cannot be greater than the current date and time.";
public bool IsOlder(DateTime thisDate, DateTime otherDate)
{
if (thisDate <= DateTime.Now && otherDate <= DateTime.Now)
{
return thisDate > otherDate;
}
throw new ArgumentOutOfRangeException(getParamName(), GreaterThanCurrentDateTimeMessage);
#region Local methods
string getParamName()
=> thisDate > DateTime.Now ? nameof(thisDate) : nameof(otherDate);
#endregion
}
}
Sample TestDataToTheoryDataSource
Class
You can easily implement a dynamic TheoryData
source class by extending the DynamicTheoryDataSource
base class with TheoryData
type data source methods. You can use these just in xUnit test framework.
The derived dynamic TheoryData
source class looks quite similar to the sample Test Framework Independent Dynamic Data Source of CsabaDu.DynamicTestData
:
using CsabaDu.DynamicTestData.xUnit.Attributes;
using CsabaDu.DynamicTestData.xUnit.DynamicDataSources;
using Xunit;
namespace CsabaDu.DynamicTestData.SampleCodes.DynamicDataSources;
class TestDataToTheoryDataSource(ArgsCode argsCode) : DynamicTheoryDataSource(argsCode)
{
private readonly DateTime DateTimeNow = DateTime.Now;
private DateTime _thisDate;
private DateTime _otherDate;
public TheoryData? IsOlderReturnsToTheoryData()
{
bool expected = true;
string definition = "thisDate is greater than otherDate";
_thisDate = DateTimeNow;
_otherDate = DateTimeNow.AddDays(-1);
addTestDataToTheoryData();
expected = false;
definition = "thisDate equals otherDate";
_otherDate = DateTimeNow;
addTestDataToTheoryData();
definition = "thisDate is less than otherDate";
_thisDate = DateTimeNow.AddDays(-1);
addTestDataToTheoryData();
return TheoryData;
#region Local methods
void addTestDataToTheoryData()
=> AddTestDataReturnsToTheoryData(definition, expected, _thisDate, _otherDate);
#endregion
}
public TheoryData? IsOlderThrowsToTheoryData()
{
string paramName = "otherDate";
_thisDate = DateTimeNow;
_otherDate = DateTimeNow.AddDays(1);
addTestDataToTheoryData();
paramName = "thisDate";
_thisDate = DateTimeNow.AddDays(1);
addTestDataToTheoryData();
return TheoryData;
#region Local methods
void addTestDataToTheoryData()
=> AddTestDataThrowsToTheoryData(getDefinition(), getExpected(), _thisDate, _otherDate);
string getDefinition()
=> $"{paramName} is greater than the current date";
ArgumentOutOfRangeException getExpected()
=> new(paramName, DemoClass.GreaterThanCurrentDateTimeMessage);
#endregion
}
}
Sample Test Classes with TheoryData
source
Note that you cannot implement IXunitSerializable
or IXunitSerializer
(xUnit.v3) interfaces any way, since TestData
types are open-generic ones. Secondary reason is that TestData
types intentionally don't have parameterless constructors. Anyway you can still use these types as dynamic test parameters or you can use the methods to generate object arrays of IXunitSerializable
elements. Ultimately you can generate xUnit-serializable data-driven test parameters as object arrays of xUnit-serializable-by-default (p.e. intristic) elements.
The individual test cases will be displayed in Test Explorer on the Test Details screen as multiple result outcomes. To have the short name of the test method in Test Explorer add the following xunit.runner.json
file to the test project:
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"methodDisplay": "method"
}
Furthermore, you should insert this item group in the xUnit project file too to have the desired result:
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Besides, note that you can have the desired test case display name in the Test Explorer just when you use the TestData
instance as the element of the generated object array, otherwise Test Explorer will display the test parameters in the default format.
Find xUnit sample codes for using TestData
instance as test method parameter:
using CsabaDu.DynamicTestData.xUnit.Attributes;
using Xunit;
namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples.TheoryDataSamples;
public sealed class DemoClassTestsTestDataToTheoryDataInstance : IDisposable
{
private readonly DemoClass _sut = new();
private static readonly TestDataToTheoryDataSource DataSource = new(ArgsCode.Instance);
public void Dispose() => DataSource.ResetTheoryData();
public static TheoryData<TestDataReturns<bool, DateTime, DateTime>>? IsOlderReturnsArgsTheoryData
=> DataSource.IsOlderReturnsToTheoryData() as TheoryData<TestDataReturns<bool, DateTime, DateTime>>;
public static TheoryData<TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime>>? IsOlderThrowsArgsTheoryData
=> DataSource.IsOlderThrowsToTheoryData() as TheoryData<TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime>>;
[Theory, MemberData(nameof(IsOlderReturnsArgsTheoryData))]
public void IsOlder_validArgs_returnsExpected(TestDataReturns<bool, DateTime, DateTime> testData)
{
// Arrange & Act
var actual = _sut.IsOlder(testData.Arg1, testData.Arg2);
// Assert
Assert.Equal(testData.Expected, actual);
}
[Theory, MemberData(nameof(IsOlderThrowsArgsTheoryData))]
public void IsOlder_invalidArgs_throwsException(TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime> testData)
{
// Arrange & Act
void attempt() => _ = _sut.IsOlder(testData.Arg1, testData.Arg2);
// Assert
var actual = Assert.Throws<ArgumentOutOfRangeException>(attempt);
Assert.Equal(testData.Expected.ParamName, actual.ParamName);
Assert.Equal(testData.Expected.Message, actual.Message);
}
}
Results in the Test Explorer:
Find xUnit sample codes for using TestData
properties' object array members as test method parameters.
using CsabaDu.DynamicTestData.xUnit.Attributes;
using Xunit;
namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples.TheoryDataSamples;
public sealed class DemoClassTestsTestDataToTheoryDataProperties : IDisposable
{
private readonly DemoClass _sut = new();
private static readonly TestDataToTheoryDataSource DataSource = new(ArgsCode.Properties);
public void Dispose() => DataSource.ResetTheoryData();
public static TheoryData<bool, DateTime, DateTime>? IsOlderReturnsArgsTheoryData
=> DataSource.IsOlderReturnsToTheoryData() as TheoryData<bool, DateTime, DateTime>;
public static TheoryData<ArgumentOutOfRangeException, DateTime, DateTime>? IsOlderThrowsArgsTheoryData
=> DataSource.IsOlderThrowsToTheoryData() as TheoryData<ArgumentOutOfRangeException, DateTime, DateTime>;
[Theory, MemberData(nameof(IsOlderReturnsArgsTheoryData))]
public void IsOlder_validArgs_returnsExpected(bool expected, DateTime thisDate, DateTime otherDate)
{
// Arrange & Act
var actual = _sut.IsOlder(thisDate, otherDate);
// Assert
Assert.Equal(expected, actual);
}
[Theory, MemberData(nameof(IsOlderThrowsArgsTheoryData))]
public void IsOlder_invalidArgs_throwsException(ArgumentOutOfRangeException expected, DateTime thisDate, DateTime otherDate)
{
// Arrange & Act
void attempt() => _ = _sut.IsOlder(thisDate, otherDate);
// Assert
var actual = Assert.Throws<ArgumentOutOfRangeException>(attempt);
Assert.Equal(expected.ParamName, actual.ParamName);
Assert.Equal(expected.Message, actual.Message);
}
}
Results in the Test Explorer:
Contributing
Contributions are welcome! Please submit a pull request or open an issue if you have any suggestions or bug reports.
License
This project is licensed under the MIT License. See the License file for details.
Contact
For any questions or inquiries, please contact CsabaDu.
FAQ
Troubleshooting
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net9.0
- CsabaDu.DynamicTestData (>= 1.0.17)
- xunit (>= 2.9.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.