Table of Contents

Value sources

In this article we will generally use CombinatorialDataAttribute. Please note that PairwiseDataAttribute is equally applicable in all these samples.

Enumerable values

Parameter types with allowed values that can already be enumerated will be provided those values by default. Consider this case of using bool as the parameter type:

[Theory, CombinatorialData]
public void CheckFileSystem(bool recursive)
{
    // verifications here
}

The CombinatorialDataAttribute (or PairwiseDataAttribute) will supply Xunit with both true and false arguments to run the test method with, resulting in two invocations of your test method with individual results reported for each invocation.

Using a C# enum type will similarly yield each enum value automatically for test case generation.

Custom-supplied values

To supply your own values to pass in for each parameter, use the CombinatorialValuesAttribute:

[Theory, CombinatorialData]
public void CheckValidAge([CombinatorialValues(5, 18, 21, 25)] int age)
{
    // verifications here
}

This will run your test method four times with each of the prescribed values.

Values over a range

To run a test with a parameter over a range of values, we have CombinatorialRangeAttribute to generate tests over intervals of integers.

[Theory, CombinatorialData]
public void CombinatorialCustomRange(
    [CombinatorialRange(0, 5)] int p1,
    [CombinatorialRange(0, 3, 2)] int p2)
{
    // Combinatorial generates these test cases:
    // 0 0
    // 1 0
    // 2 0
    // 3 0
    // 4 0
    // 0 2
    // 1 2
    // 2 2
    // 3 2
    // 4 2
}

CombinatorialRangeAttribute has two distinct constructors. When supplied with two integers from and count, Xunit will create a test case where the parameter equals from, and it will increment the parameter by 1 for count number of cases.

In the second constructor, CombinatorialRangeAttribute accepts three integer parameters. In the generated cases, the parameter value will step up from the first integer to the second integer, and the third integer specifies the interval of which to increment.

Value generated by a member

The CombinatorialMemberDataAttribute may be used to generate values for an individual Theory parameter using a static member on the test class. The static member may be a field, property or method.

A value-generating method is used here:

public static IEnumerable<int> GetRange(int start, int count)
{
    return Enumerable.Range(start, count);
}

[Theory, CombinatorialData]
public void CombinatorialMemberDataFromParameterizedMethods(
    [CombinatorialMemberData(nameof(GetRange), 0, 5)] int p1)
{
    Assert.True(true);
}

A value-generating property is used here:

public static IEnumerable<int> IntPropertyValues
{
    get
    {
        for (int i = 0; i < 5; i++)
        {
            yield return Random.Shared.Next();
        }
    }
}

[Theory, CombinatorialData]
public void CombinatorialMemberDataFromProperties(
    [CombinatorialMemberData(nameof(IntPropertyValues))] int p1)
{
    Assert.True(true);
}

A value-generating field also works:

public static readonly IEnumerable<int> IntFieldValues =
    Enumerable.Range(0, 5).Select(_ => Random.Shared.Next());

[Theory, CombinatorialData]
public void CombinatorialMemberDataFromFields(
    [CombinatorialMemberData(nameof(IntFieldValues))] int p2)
{
    Assert.True(true);
}

Randomly generated values

The CombinatorialRandomDataAttribute can be applied to theory parameters to generate random integer values. The min, max, and number of values can all be set via named parameters.

[Theory, CombinatorialData]
public void CombinatorialRandomValuesCount(
    [CombinatorialRandomData(Count = 10)] int p1)
{
    Assert.InRange(p1, 0, int.MaxValue);
}

[Theory, CombinatorialData]
public void CombinatorialRandomValuesCountMinMaxValues(
    [CombinatorialRandomData(Count = 10, Minimum = -20, Maximum = -5)] int p1)
{
    Assert.InRange(p1, -20, -5);
}