CsabaDu.DynamicTestData 1.0.5

There is a newer version of this package available.
See the version list below for details.
dotnet add package CsabaDu.DynamicTestData --version 1.0.5
                    
NuGet\Install-Package CsabaDu.DynamicTestData -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CsabaDu.DynamicTestData" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CsabaDu.DynamicTestData" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="CsabaDu.DynamicTestData" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CsabaDu.DynamicTestData --version 1.0.5
                    
#r "nuget: CsabaDu.DynamicTestData, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=CsabaDu.DynamicTestData&version=1.0.5
                    
Install CsabaDu.DynamicTestData as a Cake Addin
#tool nuget:?package=CsabaDu.DynamicTestData&version=1.0.5
                    
Install CsabaDu.DynamicTestData as a Cake Tool

<a name="top"></a>

CsabaDu.DynamicTestData

CsabaDu.DynamicTestData is a lightweight type-safe C# library designed to facilitate dynamic data-driven testing in MSTest, NUnit or xUnit framework.

Table of Contents

Description

CsabaDu.DynamicTestData provides open-generic data types and easy-to-use methods to help creating general-purpose as well as specific strongly typed data-driven test case parameters dynamically, with literal test case descriptions to display in Visual Studio Test Explorer.

It consists of easy-to-use record types to initialize, store and proceed parameters of dynamic data-driven tests, and an extendable abstract DynamicDataSource base class with fully implemented methods to create specific object arrays of the data stored in TestData records. You get ready-to-use methods to use as enumeration members of the derived dynamic data source classes.

It is a lightweight and narrow library. It does not have outer dependencies so it is portable, you can use with any test framework in Visual Studio. However consider the limitations of its usage and extensibility mentioned where applicable.

<a href="#top" class="top-link">↑ Back to top</a>

Features

  • Comprehensive support for various types used in testing.
  • Easy integration with your existing test frameworks.
  • Utilities for generating test data records in two ways for dynamic data-driven tests.
  • Extendable types to support further details or other modes of assertions.

<a href="#top" class="top-link">↑ Back to top</a>

Types

ArgsCode Enum

Every test frameworks accept object arrays as dynamic data-driven tests' data rows. The test parameters should be the object array elements. Other approach is that the object array contains a single object element, and the tests' parameters can be the properties of this object element.

CsabaDu.DynamicTestData supports both approaches, you can generate object arrays with either content. The outcome of the object array generator methods is controlled with the dedicated enum ArgsCode type parameter having two self-explanatory values:

namespace CsabaDu.DynamicTestData.DynamicDataSources;

public enum ArgsCode
{
    Instance,
    Properties,
}

ArgsCode will be used as basic parameter of the object array generator methods.

<a href="#top" class="top-link">↑ Back to top</a>

Static Extensions Class

Object array type is extended with a method to facilitate test data object arrays creation. Besides the object array which calls it, the method requires two parameters. In case of Properties value of the first ArgsCode argument the method increases the returning object array's elements with the new parameter as last one there, otherwise it returns the original object array:

namespace CsabaDu.DynamicTestData.Statics;

public static class Extensions
{
    public static object?[] Add<T>(this object?[] args, ArgsCode argsCode, T? parameter)
    => argsCode == ArgsCode.Properties ? [.. args, parameter] : args;
}

<a href="#top" class="top-link">↑ Back to top</a>

ITestData Base Interfaces

CsabaDu.DynamicTestData provides three extendable base record types, and their concrete generic implementations of strongly typed parameters with T1 - T9 open generic types.

Each TestData type implements the following interfaces:

namespace CsabaDu.DynamicTestData.TestDataTypes.Interfaces;

public interface ITestData
{
    string Definition { get; }
    string ExitMode { get; }
    string Result { get; }
    string TestCase { get; }

    object?[] ToArgs(ArgsCode argsCode);
}

public interface ITestData<out TResult> : ITestData where TResult : notnull
{
    TResult Expected { get; }
}

ITestData is the base interface of three inheritance lines. All derived types implement an abstract class each which implements a dedicated interface derived from the ITestData<out TResult> interface. Inherited types are TestData, TestDataReturns<TStruct> and TestDataThrows<TException>.

<a href="#top" class="top-link">↑ Back to top</a>

ITestData Properties

All types have five common properties.

Two properties are injected as first two parameters to each derived concrete types' constructors:

  • string Definition to describe the test case parameters to be asserted.
  • TResult Expected, a generic type property with notnull constraint.

Additional properties are generated as follows:

  • string Result property gets the appropriate string representation of the Expected property.
  • string ExitMode property gets a constant string declared in the derived types. This implementation gets the following strings in the derived types:
    • TestData: "" (virtual),
    • TestDataReturns<TStruct>: "returns" (sealed),
    • TestDataThrows<TException>: "throws" (sealed).
  • string TestCase property gets the test case description. This text is created from the other properties in the following ways:
    • If ExitMode property gets null or an empty string: $"{Description} => {Result}",
    • Otherwise: $"{Description} => {ExitMode} {Result}.

<a href="#top" class="top-link">↑ Back to top</a>

ITestData Methods

ITestData interface defines the object?[] ToArgs(ArgsCode argsCode) method only.

Intended behavior of this method is to generate an object array from the data of the ITestData instance in two ways: The returning object array should contain either the properties of the ITestData instance or the ITestData instance itself.

<a href="#top" class="top-link">↑ Back to top</a>

TestData Record Types

All concrete TestData types are inherited from the abstract record TestData type. Its primary constructor with the object?[] ToArgs(ArgsCode argsCode) method's virtual implementation looks like:

namespace CsabaDu.DynamicTestData.TestDataTypes;

public abstract record TestData(string Definition) : ITestData
{
    public virtual object?[] ToArgs(ArgsCode argsCode) => argsCode switch
    {
        ArgsCode.Instance => [this],
        ArgsCode.Properties => [TestCase],
        _ => throw new InvalidEnumArgumentException(nameof(argsCode), (int)(object)argsCode, typeof(ArgsCode)),
    };
}

In the derived concrete TestData types the overriden object?[] ToArgs(ArgsCode argsCode) methods will increase the returning object array of the parent record with the recently added parameter in case of ArgsCode.Properties parameter, otherwise it will return an object array containing the given instance. Using the object?[] Add<T>(this object?[] args, T? arg) extension method, the overriden methods' implementations are uniform as you will see.

This type overrides and seals the string ToString() method with returning the TestCase property's value. When the instance is used as test method parameter, the test case display name will be the string representation of the instance.

<a href="#top" class="top-link">↑ Back to top</a>

Derived TestData Types

All derived types of TestData base type implement the ITestdata<out TResult> : ITestData interface. TestData concrete types will inherit direcly from the abstract TestData record, other types will inherit via intermediate abstract types.

Other inheritance line of the ITestData<out TResult> interface remains abstract and each concrete type inherits one. You can approach the different specified types having same test parameter types by calling these Interfaces:

namespace CsabaDu.DynamicTestData.TestDataTypes.Interfaces;

public interface ITestData<out TResult, out T1> : ITestData<TResult> where TResult : notnull
{
    T1? Arg1 { get; }
}

public interface ITestData<out TResult, out T1, out T2> : ITestData<TResult, T1> where TResult : notnull
{
    T2? Arg2 { get; }
}

// And similar extended inheritances till T9 type argument.

See the whole ITestData interface inheritance structure on the below picture:

TestDataInterfaces

<a href="#top" class="top-link">↑ Back to top</a>

TestData

Implements the following interface:

namespace CsabaDu.DynamicTestData.TestDataTypes.Interfaces;

public interface ITestData<string> : ITestData
  • General purposes type ITestData.
  • Expected property's type is string. The expected test case result should be written down literally.

Concrete TestData types primary constructors with the overriden object?[] ToArgs(ArgsCode argsCode) methods look like:

namespace CsabaDu.DynamicTestData.TestDataTypes;

public record TestData<T1>(string Definition, string Expected, T1? Arg1)
: TestData(Definition), ITestData<string, T1>
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg1);
}

public record TestData<T1, T2>(string Definition, string Expected, T1? Arg1, T2? Arg2)
: TestData<T1>(Definition, Expected, Arg1), ITestData<string, T1, T2>
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg2);
}

// And similar extended inheritances till T9 type argument.

TestCase displays in text explorer like:

$"{Definition} => {string.IsNullOrEmpty(Expected) ? nameof(Expected) : Expected}

<a href="#top" class="top-link">↑ Back to top</a>

TestDataReturns

Implements the following interface:

namespace CsabaDu.DynamicTestData.TestDataTypes.Interfaces;

public interface ITestDataReturns<out TStruct> : ITestData<TStruct> where TStruct : struct;
  • Designed to assert the comparison of numbers, booleans, enums, and other struct types' values.
  • Expected property's type is struct.

The abstract TestDataReturns<TStruct> type and its concrete derived types' primary constructors with the overriden object?[] ToArgs(ArgsCode argsCode) methods look like:

namespace CsabaDu.DynamicTestData.TestDataTypes;

public abstract record TestDataReturns<TStruct>(string Definition, TStruct Expected)
: TestData(Definition), ITestDataReturns<out TStruct>
where TStruct : struct
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Expected);
}

public record TestDataReturns<TStruct, T1>(string Definition, TStruct Expected, T1? Arg1)
: TestDataReturns<TStruct>(Definition, Expected), ITestData<TStruct, T1>
where TStruct : struct
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg1);
}

public record TestDataReturns<TStruct, T1, T2>(string Definition, TStruct Expected, T1? Arg1, T2? Arg2)
: TestDataReturns<TStruct, T1>(Definition, Expected, Arg1), ITestData<TStruct, T1, T2>
where TStruct : struct
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg2);
}

// And similar extended inheritances till T9 type argument.

TestCase displays in text explorer like:

$"{Definition} => returns {Expected.ToString() ?? nameof(Expected)}"

<a href="#top" class="top-link">↑ Back to top</a>

TestDataThrows

Implements the following interface:

namespace CsabaDu.DynamicTestData.TestDataTypes.Interfaces;

public interface ITestDataThrows<out TException> : ITestData<TException> where TException : Exception;
  • Designed for test cases where the expected result to be asserted is a thrown Exception.
  • Expected property's type is Exception.

The abstract TestDataThrows<TException> type and its concrete derived types' primary constructors with the overriden object?[] ToArgs(ArgsCode argsCode) methods look like:

namespace CsabaDu.DynamicTestData.TestDataTypes;

public abstract record TestDataThrows<TException>(string Definition, TException Expected, string? ParamName, string? Message)
: TestData(Definition), ITestDataThrows<out TException>
where TException : Exception
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Expected);
}

public record TestDataThrows<TException, T1>(string Definition, TException Expected, string? ParamName, string? Message, T1? Arg1)
: TestDataThrows<TException>(Definition, Expected, ParamName, Message), ITestData<TException, T1>
where TException : Exception
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg1);
}

public record TestDataThrows<TException, T1, T2>(string Definition, TException Expected, string? ParamName, string? Message, T1? Arg1, T2? Arg2)
: TestDataThrows<TException, T1>(Definition, Expected, ParamName, Message, Arg1), ITestData<TException, T1, T2>
where TException : Exception
{
    public override object?[] ToArgs(ArgsCode argsCode)
    => base.ToArgs(argsCode).Add(argsCode, Arg2);
}

// And similar extended inheritances till T9 type argument.

TestCase displays in text explorer like:

$"{Definition} => throws {typeof(TException).Name}"

<a href="#top" class="top-link">↑ Back to top</a>

Abstract DynamicDataSource Class

This class contains the methods to create specific object arrays for dynamic data-driven tests' data row purposes from every TestData types. Once you call an object array generator method of the class, you create a new TestData child instance inside and call its object?[] ToArgs(ArgsCode argsCode) method to create the object array for dynamic test data record purposes.

However DynamicDataSource class implements all necessary methods for test data preparation, it is marked as abstract. Intended usage is to

  • extend this class for each test class separately,
  • implement the necessary specific methods in the derived class with IEnumerable<object[]> returning types, and
  • declare a static instance of the derived class in the test class where it is going to be used.

You can implement its children as test framework independent portable dynamic data source types. Moreover, using a test framework in the derived classes, you can create specific types either like TestCaseData type data rows of NUnit. You will find sample codes of these in the Advanced Usage section below.

<a href="#top" class="top-link">↑ Back to top</a>

ArgsCode Property

ArgsCode ArgsCode is the only property of DynamicDataSource class. This property is marked as protected. It should be initalized with the constructor parameter of the class. This property will be the parameter of the ToArgs methods called by the object array generator methods of the class

<a href="#top" class="top-link">↑ Back to top</a>

Object Array Generator Methods

DynamicDataSource class provides a dedicated object array generator each TestData type. The methods' parameters types and sequences are the same as the constructors' parameters of the related TestData types.

<a href="#top" class="top-link">↑ Back to top</a>

TestDataToArgs
  • Signature:

object?[] TestDataToArgs<T1...T9>(string definition, string expected, T1? arg1 ... T9? arg9).

  • In case of ArgsCode.Properties parameter, the returning object array content is as follows:

[TestCase, Arg1 ... Arg9].

<a href="#top" class="top-link">↑ Back to top</a>

TestDataReturnsToArgs
  • Signature:

object?[] TestDataReturnsToArgs<TStruct, T1...T9>(string definition, TStruct Expected, T1? arg1 ... T9? arg9).

  • In case of ArgsCode.Properties parameter, the returning object array content is as follows:

[TestCase, Expected, Arg1 ... Arg9].

<a href="#top" class="top-link">↑ Back to top</a>

TestDataThrowsToArgs
  • Signature:

object?[] TestDataThrowsToArgs<TException, T1...T9>(string definition, TException expected, T1? arg1 ... T9? arg9).

  • In case of ArgsCode.Properties parameter, the returning object array content is as follows:

[TestCase, Expected, Arg1 ... Arg9].

<a href="#top" class="top-link">↑ Back to top</a>

Static GetDisplayName method

This method is prepared to facilitate displaying the required literal testcase description in MSTest and NUnit framewoks. You will find sample code for MSTest usage in the Usage, for NUnit usage in the Advanced Usage sections below.

The method is implemented to support initializing the MSTest framework's DynamicDataAttribute.DynamicDataDisplayName property. Following the testmethod's name, the injected object array's first element will be used as string. This element in case of ArgsCode.Properties is the TestCase property of the instance, and the instance's string representation in case of ArgsCode.Instance. This is the TestCase property's value either as the ToString() method returns that.

namespace CsabaDu.DynamicTestData.DynamicDataSources;

public abstract class DynamicDataSource(ArgsCode argsCode)
{
    public static string GetDisplayName(string testMethodName, object?[] args)
    => $"{testMethodName}({args[0]})";

    // Other members here
}

<a href="#top" class="top-link">↑ Back to top</a>

Usage

Here are some basic examples of how to use CsabaDu.DynamicTestData 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.

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
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

Test Framework Independent Dynamic Data Source

You can easily implement test framework independent dynamic data source by extending the DynamicDataSource base class with IEnumerable<object?[]> type data source methods. You can use these directly in either test framework.

The 'native' dynamic data source class looks like:

namespace CsabaDu.DynamicTestData.SampleCodes.DynamicDataSources;

public class NativeTestDataSource(ArgsCode argsCode) : DynamicDataSource(argsCode)
{
    private readonly DateTime DateTimeNow = DateTime.Now;

    private DateTime _thisDate;
    private DateTime _otherDate;

    public IEnumerable<object?[]> IsOlderReturnsArgsToList()
    {
        bool expected = true;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(-1);
        yield return testDataToArgs("thisDate is greater than otherDate");

        expected = false;
        _otherDate = DateTimeNow;
        yield return testDataToArgs("thisDate equals otherDate");

        _thisDate = DateTimeNow.AddDays(-1);
        yield return testDataToArgs("thisDate is less than otherDate");

        #region local methods
        object?[] testDataToArgs(string definition)
        => TestDataReturnsToArgs(definition, expected, _thisDate, _otherDate);
        #endregion
    }

    public IEnumerable<object?[]> IsOlderThrowsArgsToList()
    {
        string paramName = "otherDate";
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(1);
        yield return testDataToArgs();

        paramName = "thisDate";
        _thisDate = DateTimeNow.AddDays(1);
        yield return testDataToArgs();

        #region Local methods
        object?[] testDataToArgs()
        => TestDataThrowsToArgs(getDefinition(), getExpected(), _thisDate, _otherDate);

        string getDefinition()
        => $"{paramName} is greater than the current date";

        ArgumentOutOfRangeException getExpected()
        => new(paramName, DemoClass.GreaterThanCurrentDateTimeMessage);
        #endregion
    }
}

You can use this dynamic data source class initialized either with ArgsCode.Instance or ArgsCode.Properties in any test framework. You will find examples of both option for each yet. However, note that NUnit will display the test case as desired just with ArgsCode.Instance injection.

<a href="#top" class="top-link">↑ Back to top</a>

Usage in MSTest

Find MSTest sample codes for using TestData instance as test method parameter:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CsabaDu.DynamicTestData.SampleCodes.MSTestSamples;

[TestClass]
public sealed class DemoClassTestsInstance
{
    private readonly DemoClass _sut = new();
    private static readonly NativeTestDataSource DataSource = new(ArgsCode.Instance);
    private const string DisplayName = nameof(GetDisplayName);

    private static IEnumerable<object?[]> IsOlderReturnsArgsList
    => DataSource.IsOlderReturnsArgsToList();

    private static IEnumerable<object?[]> IsOlderThrowsArgsList
    => DataSource.IsOlderThrowsArgsToList();

    public static string GetDisplayName(MethodInfo testMethod, object?[] args)
    => DynamicDataSource.GetDisplayName(testMethod.Name, args);

    [TestMethod]
    [DynamicData(nameof(IsOlderReturnsArgsList), DynamicDataDisplayName = DisplayName)]
    public void IsOlder_validArgs_returnsExpected(TestDataReturns<bool, DateTime, DateTime> testData)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        Assert.AreEqual(testData.Expected, actual);
    }

    [TestMethod]
    [DynamicData(nameof(IsOlderThrowsArgsList), DynamicDataDisplayName = DisplayName)]
    public void IsOlder_invalidArgs_throwsException(TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime> testData)
    {
        // Arrange & Act
        void attempt() => _ = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        var actual = Assert.ThrowsException<ArgumentOutOfRangeException>(attempt);
        Assert.AreEqual(testData.Expected.ParamName, actual.ParamName);
        Assert.AreEqual(testData.Expected.Message, actual.Message);
    }
}

Find MSTest sample codes for using TestData properties'object array members as test method parameters.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CsabaDu.DynamicTestData.SampleCodes.MSTestSamples;

[TestClass]
public sealed class DemoClassTestsProperties
{
    private readonly DemoClass _sut = new();
    private static readonly NativeTestDataSource DataSource = new(ArgsCode.Properties);
    private const string DisplayName = nameof(GetDisplayName);
    private const TestDataSourceUnfoldingStrategy Fold = TestDataSourceUnfoldingStrategy.Fold;

    private static IEnumerable<object?[]> IsOlderReturnsArgsList
    => DataSource.IsOlderReturnsArgsToList();

    private static IEnumerable<object?[]> IsOlderThrowsArgsList
    => DataSource.IsOlderThrowsArgsToList();

    public static string GetDisplayName(MethodInfo testMethod, object?[] args)
    => DynamicDataSource.GetDisplayName(testMethod.Name, args);

    [TestMethod]
    [DynamicData(nameof(IsOlderReturnsArgsList), UnfoldingStrategy = Fold, DynamicDataDisplayName = DisplayName)]
    public void IsOlder_validArgs_returnsExpected(string testCase, bool expected, DateTime thisDate, DateTime otherDate)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(thisDate, otherDate);

        // Assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    [DynamicData(nameof(IsOlderThrowsArgsList), UnfoldingStrategy = Fold, DynamicDataDisplayName = DisplayName)]
    public void IsOlder_invalidArgs_throwsException(string testCase, ArgumentOutOfRangeException expected, DateTime thisDate, DateTime otherDate)
    {
        // Arrange & Act
        void attempt() => _ = _sut.IsOlder(thisDate, otherDate);

        // Assert
        var actual = Assert.ThrowsException<ArgumentOutOfRangeException>(attempt);
        Assert.AreEqual(expected.ParamName, actual.ParamName);
        Assert.AreEqual(expected.Message, actual.Message);
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

Usage in NUnit

Find MSTest sample codes for using TestData instance as test method parameter:

using NUnit.Framework;

namespace CsabaDu.DynamicTestData.SampleCodes.NUnitSamples;

[TestFixture]
public sealed class DemoClassTestsInstance
{
    private readonly DemoClass _sut = new();
    private static readonly NativeTestDataSource DataSource = new(ArgsCode.Instance);

    private static IEnumerable<object?[]> IsOlderReturnsArgsToList()
    => DataSource.IsOlderReturnsArgsToList();

    private static IEnumerable<object?[]> IsOlderThrowsArgsToList()
    => DataSource.IsOlderThrowsArgsToList();

    [TestCaseSource(nameof(IsOlderReturnsArgsToList))]
    public void IsOlder_validArgs_returnsExpected(TestDataReturns<bool, DateTime, DateTime> testData)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        Assert.That(actual, Is.EqualTo(testData.Expected));
    }

    [TestCaseSource(nameof(IsOlderThrowsArgsToList))]
    public void IsOlder_invalidArgs_throwsException(TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime> testData)
    {
        // Arrange & Act
        void attempt() => _ = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        Assert.Multiple(() =>
        {
            var actual = Assert.Throws<ArgumentOutOfRangeException>(attempt);
            Assert.That(actual?.ParamName, Is.EqualTo(testData.Expected.ParamName));
            Assert.That(actual?.Message, Is.EqualTo(testData.Expected.Message));
        });
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

Usage in xUnit

However CsabaDu.DynamicTestData works well with xUnit, 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 (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>

Find xUnit sample codes for using TestData instance as test method parameter:

using Xunit;

namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples;

public sealed class DemoClassTestsInstance
{
    private readonly DemoClass _sut = new();
    private static readonly NativeTestDataSource DataSource = new(ArgsCode.Instance);

    public static IEnumerable<object?[]> IsOlderReturnsArgsList
    => DataSource.IsOlderReturnsArgsToList();

    public static IEnumerable<object?[]> IsOlderThrowsArgsList
    => DataSource.IsOlderThrowsArgsToList();

    [Theory, MemberData(nameof(IsOlderReturnsArgsList))]
    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(IsOlderThrowsArgsList))]
    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);
    }
}

Find xUnit sample codes for using TestData properties'object array members as test method parameters.

using Xunit;

namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples;

public sealed class DemoClassTestsProperties
{
    private readonly DemoClass _sut = new();
    private static readonly NativeTestDataSource DataSource = new(ArgsCode.Properties);

    public static IEnumerable<object?[]> IsOlderReturnsArgsList
    => DataSource.IsOlderReturnsArgsToList();

    public static IEnumerable<object?[]> IsOlderThrowsArgsList
    => DataSource.IsOlderThrowsArgsToList();

    [Theory, MemberData(nameof(IsOlderReturnsArgsList))]
    public void IsOlder_validArgs_returnsExpected(string testCase, bool expected, DateTime thisDate, DateTime otherDate)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(thisDate, otherDate);

        // Assert
        Assert.Equal(expected, actual);
    }

    [Theory, MemberData(nameof(IsOlderThrowsArgsList))]
    public void IsOlder_invalidArgs_throwsException(string testCase, 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);
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

Advanced Usage

Besides generating object array lists for dynamic data-driven tests, you can use CsabaDu.DynamicTestData to support own type creation of the selected test framework.

<a href="#top" class="top-link">↑ Back to top</a>

Using TestCaseData type of NUnit

You can generate TestCaseData type of NUnit from TestData, since its constructor's parameter should be an object array. TestCaseData instances grant other features supporting meta data completion, and methods like SetName to set display name of the test case.

using NUnit.Framework;

namespace CsabaDu.DynamicTestData.SampleCodes.DynamicDataSources;

public class TestCaseDataSource(ArgsCode argsCode) : DynamicDataSource(argsCode)
{
    private readonly DateTime DateTimeNow = DateTime.Now;

    private DateTime _thisDate;
    private DateTime _otherDate;

    private TestCaseData TestDataToTestCaseData<TResult>(Func<object?[]> testDataToArgs, string testMethodName) where TResult : notnull
    {
        object?[] args = testDataToArgs();
        string displayName = GetDisplayName(testMethodName, args);
        TestCaseData testCaseData = ArgsCode switch
        {
            ArgsCode.Instance => new(args),
            ArgsCode.Properties => new(args[1..]),
            _ => throw new InvalidOperationException("ArgsCode property has invalid value."),
        };

        return testCaseData.SetName(displayName);
    }

    public IEnumerable<TestCaseData> IsOlderReturnsTestCaseDataToList(string testMethodName)
    {
        bool expected = true;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(-1);
        string definition = "thisDate is greater than otherDate";
        yield return testDataToTestCaseData();

        expected = false;
        _otherDate = DateTimeNow;
        definition = "thisDate equals otherDate";
        yield return testDataToTestCaseData();

        _thisDate = DateTimeNow.AddDays(-1);
        definition = "thisDate is less than otherDate";
        yield return testDataToTestCaseData();

        #region local methods
        TestCaseData testDataToTestCaseData()
        => TestDataToTestCaseData<bool>(testDataToArgs, testMethodName);

        object?[] testDataToArgs()
        => TestDataReturnsToArgs(definition, expected, _thisDate, _otherDate);
        #endregion
    }

    public IEnumerable<TestCaseData> IsOlderThrowsTestCaseDataToList(string testMethodName)
    {
        string paramName = "otherDate";
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(1);
        yield return testDataToTestCaseData();

        paramName = "thisDate";
        _thisDate = DateTimeNow.AddDays(1);
        yield return testDataToTestCaseData();

        #region Local methods
        TestCaseData testDataToTestCaseData()
        => TestDataToTestCaseData<ArgumentOutOfRangeException>(testDataToArgs, testMethodName);

        object?[] testDataToArgs()
        => TestDataThrowsToArgs(getDefinition(), getExpected(), _thisDate, _otherDate);

        string getDefinition()
        => $"{paramName} is greater than the current date";

        ArgumentOutOfRangeException getExpected()
        => new(paramName, DemoClass.GreaterThanCurrentDateTimeMessage);
        #endregion
    }
}

Find NUnit sample codes for using TestData instance's array as TesCaseData parameter:

using NUnit.Framework;

namespace CsabaDu.DynamicTestData.SampleCodes.NUnitSamples;

[TestFixture]
public sealed class DemoClassTestsInstanceWithTestCaseData
{
    private readonly DemoClass _sut = new();
    private static readonly TestCaseDataSource DataSource = new(ArgsCode.Instance);

    private static IEnumerable<TestCaseData> IsOlderReturnsTestCaseDataToList()
    => DataSource.IsOlderReturnsTestCaseDataToList(nameof(IsOlder_validArgs_returnsExpected));

    private static IEnumerable<TestCaseData> IsOlderThrowsTestCaseDataToList()
    => DataSource.IsOlderThrowsTestCaseDataToList(nameof(IsOlder_invalidArgs_throwsException));

    [TestCaseSource(nameof(IsOlderReturnsTestCaseDataToList))]
    public void IsOlder_validArgs_returnsExpected(TestDataReturns<bool, DateTime, DateTime> testData)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        Assert.That(actual, Is.EqualTo(testData.Expected));
    }

    [TestCaseSource(nameof(IsOlderThrowsTestCaseDataToList))]
    public void IsOlder_invalidArgs_throwsException(TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime> testData)
    {
        // Arrange & Act
        void attempt() => _ = _sut.IsOlder(testData.Arg1, testData.Arg2);

        // Assert
        Assert.Multiple(() =>
        {
            var actual = Assert.Throws<ArgumentOutOfRangeException>(attempt);
            Assert.That(actual?.ParamName, Is.EqualTo(testData.Expected.ParamName));
            Assert.That(actual?.Message, Is.EqualTo(testData.Expected.Message));
        });
    }
}

Find NUnit sample codes for using TestData properties' array as TesCaseData parameter:

using NUnit.Framework;

namespace CsabaDu.DynamicTestData.SampleCodes.NUnitSamples;

[TestFixture]
public sealed class DemoClassTestsPropertiesWithTestCaseData
{
    private readonly DemoClass _sut = new();
    private static readonly TestCaseDataSource DataSource = new(ArgsCode.Properties);

    private static IEnumerable<TestCaseData> IsOlderReturnsTestCaseDataToList()
    => DataSource.IsOlderReturnsTestCaseDataToList(nameof(IsOlder_validArgs_returnsExpected));

    private static IEnumerable<TestCaseData> IsOlderThrowsTestCaseDataToList()
    => DataSource.IsOlderThrowsTestCaseDataToList(nameof(IsOlder_invalidArgs_throwsException));

    [TestCaseSource(nameof(IsOlderReturnsTestCaseDataToList))]
    public void IsOlder_validArgs_returnsExpected(bool expected, DateTime thisDate, DateTime otherDate)
    {
        // Arrange & Act
        var actual = _sut.IsOlder(thisDate, otherDate);

        // Assert
        Assert.That(actual, Is.EqualTo(expected));
    }

    [TestCaseSource(nameof(IsOlderThrowsTestCaseDataToList))]
    public void IsOlder_invalidArgs_throwsException(ArgumentOutOfRangeException expected, DateTime thisDate, DateTime otherDate)
    {
        // Arrange & Act
        void attempt() => _ = _sut.IsOlder(thisDate, otherDate);

        // Assert
        Assert.Multiple(() =>
        {
            var actual = Assert.Throws<ArgumentOutOfRangeException>(attempt);
            Assert.That(actual?.ParamName, Is.EqualTo(expected.ParamName));
            Assert.That(actual?.Message, Is.EqualTo(expected.Message));
        });
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

Using TheoryData type of xUnit

TheoryData is a generic type safe data source type of xUnit which implements the generic IEnumerable interface. You can use TestData types as TheoryData type parameter as well as its elements. In order to simplify the implementation, you may better use the interface ITestData generic interface types.

using Xunit;

namespace CsabaDu.DynamicTestData.SampleCodes.DynamicDataSources;

public class TheoryDataSource
{
    private readonly DateTime DateTimeNow = DateTime.Now;
    private const string thisDateIsGreaterThanOtherDate = $"{thisDateName} is greater than {otherDateName}";
    private const string thisDateIsLessThanOtherDate = $"{thisDateName} is less than {otherDateName}";
    private const string thisDateEqualsOtherDate = $"{thisDateName} equals {otherDateName}";
    private const string thisDateName = "thisDate";
    private const string otherDateName = "otherDate";

    private DateTime _thisDate;
    private DateTime _otherDate;
    private ITestData? _testData;

    private void AddTestDataInstance<TResult>(TheoryData<ITestData<TResult, DateTime, DateTime>> theoryData) where TResult : notnull
    => theoryData.Add((_testData as ITestData<TResult, DateTime, DateTime>)!);

    private void AddTestDataProperties<TResult>(TheoryData<TResult, DateTime, DateTime> theoryData) where TResult : notnull
    {
        var testData = _testData as ITestData<TResult, DateTime, DateTime>;
        theoryData.Add(testData!.Expected, testData.Arg1, testData.Arg2);
    }

    private void SetTestDataThrows(string paramName)
    {
        _testData = new TestDataThrows<ArgumentOutOfRangeException, DateTime, DateTime>(getDefinition(), getExpected(), _thisDate, _otherDate);

        string getDefinition()
        => $"{paramName} is greater than the current date";

        ArgumentOutOfRangeException getExpected()
        => new(paramName, DemoClass.GreaterThanCurrentDateTimeMessage);
    }

    private void SetTestDataReturns(string definition, bool expected)
    => _testData = new TestDataReturns<bool, DateTime, DateTime>(definition, expected, _thisDate, _otherDate);

    public TheoryData<ITestData<bool, DateTime, DateTime>> IsOlderReturnsInstanceToTheoryData()
    {
        var theoryData = new TheoryData<ITestData<bool, DateTime, DateTime>>();

        bool expected = true;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(-1);
        addTestData(thisDateIsGreaterThanOtherDate);

        expected = false;
        _otherDate = DateTimeNow;
        addTestData(thisDateEqualsOtherDate);

        _thisDate = DateTimeNow.AddDays(-1);
        addTestData(thisDateIsLessThanOtherDate);

        return theoryData;

        #region local methods
        void addTestData(string definition)
        {
            SetTestDataReturns(definition, expected);
            AddTestDataInstance(theoryData);
        }
        #endregion
    }

    public TheoryData<ITestData<ArgumentOutOfRangeException, DateTime, DateTime>> IsOlderThrowsInstanceToTheoryData()
    {
        var theoryData = new TheoryData<ITestData<ArgumentOutOfRangeException, DateTime, DateTime>>();

        string paramName = otherDateName;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(1);
        addTestData();

        paramName = thisDateName;
        _thisDate = DateTimeNow.AddDays(1);
        addTestData();

        return theoryData;

        #region Local methods
        void addTestData()
        {
            SetTestDataThrows(paramName);
            AddTestDataInstance(theoryData);
        }
        #endregion
    }

    public TheoryData<bool, DateTime, DateTime> IsOlderReturnsPropertiesToTheoryData()
    {
        var theoryData = new TheoryData<bool, DateTime, DateTime>();

        bool expected = true;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(-1);
        addTestData(thisDateIsGreaterThanOtherDate);

        expected = false;
        _otherDate = DateTimeNow;
        addTestData(thisDateEqualsOtherDate);

        _thisDate = DateTimeNow.AddDays(-1);
        addTestData(thisDateIsLessThanOtherDate);

        return theoryData;

        #region local methods
        void addTestData(string definition)
        {
            SetTestDataReturns(definition, expected);
            AddTestDataProperties(theoryData);
        }
        #endregion
    }


    public TheoryData<ArgumentOutOfRangeException, DateTime, DateTime> IsOlderThrowsPropertiesToTheoryData()
    {
        var theoryData = new TheoryData<ArgumentOutOfRangeException, DateTime, DateTime>();

        string paramName = otherDateName;
        _thisDate = DateTimeNow;
        _otherDate = DateTimeNow.AddDays(1);
        addTestData();

        paramName = thisDateName;
        _thisDate = DateTimeNow.AddDays(1);
        addTestData();

        return theoryData;

        #region Local methods
        void addTestData()
        {
            SetTestDataThrows(paramName);
            AddTestDataProperties(theoryData);
        }
        #endregion
    }
}

When using TheoryData as data source type in xUnit test class, the MemberDataAttribute detects the notated test method's arguments and the compiler generates error if the constructor parameters' types and the TheoryData type parameters are different. This means that using TheoryData makes our tests type safe indeed.

Find xUnit sample codes for using TestData instance as TheoryData element:

using Xunit;

namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples;

public sealed class DemoClassTestsInstanceWithTheoryData
{
    private readonly DemoClass _sut = new();
    private static readonly TheoryDataSource DataSource = new();

    public static TheoryData<ITestData<bool, DateTime, DateTime>> IsOlderReturnsArgsTheoryData
    => DataSource.IsOlderReturnsInstanceToTheoryData();

    public static TheoryData<ITestData<ArgumentOutOfRangeException, DateTime, DateTime>> IsOlderThrowsArgsTheoryData
    => DataSource.IsOlderThrowsInstanceToTheoryData();

    [Theory, MemberData(nameof(IsOlderReturnsArgsTheoryData))]
    public void IsOlder_validArgs_returnsExpected(ITestData<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(ITestData<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);
    }
}

The limitations mentioned in the Usage in xUnit section are applicable here. Besides, you will detect that when TheoryData elements are intristics only, the Test Explorer will display each test case like individual test methods yet.

Find xUnit sample codes for using TestData properties as TheoryData elements:

using Xunit;

namespace CsabaDu.DynamicTestData.SampleCodes.xUnitSamples;

public sealed class DemoClassTestsPropertiesWithTheoryData
{
    private readonly DemoClass _sut = new();
    private static readonly TheoryDataSource DataSource = new();

    public static TheoryData<bool, DateTime, DateTime> IsOlderReturnsArgsTheoryData
    => DataSource.IsOlderReturnsPropertiesToTheoryData();

    public static TheoryData<ArgumentOutOfRangeException, DateTime, DateTime> IsOlderThrowsArgsTheoryData
    => DataSource.IsOlderThrowsPropertiesToTheoryData();

    [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);
    }
}

<a href="#top" class="top-link">↑ Back to top</a>

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

  • How do I install the library? You can install it via NuGet Package Manager using Install-Package CsabaDu.DynamicTestData.

Troubleshooting

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on CsabaDu.DynamicTestData:

Package Downloads
CsabaDu.DynamicTestData.NUnit

Lightweight, robust type-safe C# framework designed to facilitate dynamic data-driven testing in NUnit framework, by providing a simple and intuitive way to generate `TestCaseData` instances or object arrays at runtime, based on `CsabaDu.DynamicTestData` features.

CsabaDu.DynamicTestData.xUnit

Lightweight, robust type-safe C# framework designed to facilitate dynamic data-driven testing in xUnit framework, by providing a simple and intuitive way to generate `TheoryData` instances or object arrays at runtime, based on `CsabaDu.DynamicTestData` features.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.1 134 4/2/2025
1.2.0 149 3/28/2025
1.1.1 115 3/27/2025
1.1.0 111 3/27/2025
1.0.17 176 3/19/2025
1.0.16 209 3/19/2025 1.0.16 is deprecated.
1.0.15 111 3/15/2025
1.0.14 177 3/13/2025
1.0.13 246 3/8/2025
1.0.12 277 3/5/2025
1.0.11 175 2/27/2025
1.0.10 508 2/22/2025
1.0.9 88 2/21/2025
1.0.8 101 2/21/2025
1.0.7 86 2/21/2025
1.0.6 96 2/19/2025
1.0.5 102 2/18/2025
1.0.4 92 2/18/2025
1.0.3 93 2/17/2025
1.0.2 105 2/17/2025
1.0.1 142 2/15/2025
1.0.0 91 2/14/2025

README.md updated with NUnit sample codes and ITestData class diagram