Pass Complex Parameters to [Theory]

Pass complex parameters to [Theory]

There are many xxxxData attributes in XUnit. Check out for example the MemberData attribute.

You can implement a property that returns IEnumerable<object[]>. Each object[] that this method generates will be then "unpacked" as a parameters for a single call to your [Theory] method.

See i.e. these examples from here

Here are some examples, just for a quick glance.

MemberData Example: just here at hand

public class StringTests2
{
[Theory, MemberData(nameof(SplitCountData))]
public void SplitCount(string input, int expectedCount)
{
var actualCount = input.Split(' ').Count();
Assert.Equal(expectedCount, actualCount);
}

public static IEnumerable<object[]> SplitCountData =>
new List<object[]>
{
new object[] { "xUnit", 1 },
new object[] { "is fun", 2 },
new object[] { "to test with", 3 }
};
}

XUnit < 2.0: Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

ClassData Example

public class StringTests3
{
[Theory, ClassData(typeof(IndexOfData))]
public void IndexOf(string input, char letter, int expected)
{
var actual = input.IndexOf(letter);
Assert.Equal(expected, actual);
}
}

public class IndexOfData : IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "hello world", 'w', 6 },
new object[] { "goodnight moon", 'w', -1 }
};

public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }

IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}

XUnit >= 2.0: Instead of ClassData, now there's an 'overload' of [MemberData] that allows to use static members from other classes. Examples below have been updated to use it, since XUnit < 2.x is pretty ancient now.
Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

MemberData Example: look there to another type

public class StringTests3
{
[Theory, MemberData(nameof(IndexOfData.SplitCountData), MemberType = typeof(IndexOfData))]
public void IndexOf(string input, char letter, int expected)
{
var actual = input.IndexOf(letter);
Assert.Equal(expected, actual);
}
}

public class IndexOfData : IEnumerable<object[]>
{
public static IEnumerable<object[]> SplitCountData =>
new List<object[]>
{
new object[] { "hello world", 'w', 6 },
new object[] { "goodnight moon", 'w', -1 }
};
}

Disclaimer :)

Last time checked @20210903 with dotnetfiddle.net on C# 5.0 and xunit 2.4.1 .. and failed. I couldn't mix-in a test-runner into that fiddle. But at least it compiled fine. Note that this was originally written years ago, things changed a little. I fixed them according to my hunch and comments. So.. it may contain inobvious typos, otherwise obvious bugs that would instantly pop up at runtime, and traces of milk & nuts.

Give object as parameter for test

Assuming I understand your goal, I see two ways:

  1. Using InlineDataAttribute and passing Types
  2. Using MemberDataAttribute (PropertyData in xunit.net v1)

    [Theory]
    [InlineData(typeof(BaseClass))]
    [InlineData(typeof(DerivedClass))]
    public void Test_Stuff(Type type/*BaseClass obj*/)
    {
    var obj = Activator.CreateInstance(type) as BaseClass;
    CheckConstrain(obj);
    }

    [Theory]
    [MemberData("GetObjects")]
    public void Test_Stuff2(BaseClass obj)
    {
    CheckConstrain(obj);
    }

    public static IEnumerable<object[]> GetObjects
    {
    get
    {
    return new[]
    {
    new object[] { new BaseClass() },
    new object[] { new DerivedClass() }
    };
    }
    }

    private static void CheckConstrain(BaseClass baseClass)
    {
    Assert.True(baseClass.Foo() <= 1);
    }

See also this related answer Pass complex parameters to [Theory]

Is it possible to add inline C# objects to a theory in xUnit?

You can use MemberData attribute for that. It should return IEnumerable<object[]>, where every item represents a set of parameters for your test method (in your case only one Card parameter, so only one item)

public static IEnumerable<object[]> Cards
{
get
{
yield return new object[] { Card.SevenOfHearts };
yield return new object[] { Card.SevenOfSpades };
yield return new object[] { Card.SevenOfDiamonds };
yield return new object[] { Card.SevenOfClubs };
}
}

[Theory]
[MemberData(nameof(Cards))]
void Test(Card card)
{
}

Pass complex parameters to [Theory]

There are many xxxxData attributes in XUnit. Check out for example the MemberData attribute.

You can implement a property that returns IEnumerable<object[]>. Each object[] that this method generates will be then "unpacked" as a parameters for a single call to your [Theory] method.

See i.e. these examples from here

Here are some examples, just for a quick glance.

MemberData Example: just here at hand

public class StringTests2
{
[Theory, MemberData(nameof(SplitCountData))]
public void SplitCount(string input, int expectedCount)
{
var actualCount = input.Split(' ').Count();
Assert.Equal(expectedCount, actualCount);
}

public static IEnumerable<object[]> SplitCountData =>
new List<object[]>
{
new object[] { "xUnit", 1 },
new object[] { "is fun", 2 },
new object[] { "to test with", 3 }
};
}

XUnit < 2.0: Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

ClassData Example

public class StringTests3
{
[Theory, ClassData(typeof(IndexOfData))]
public void IndexOf(string input, char letter, int expected)
{
var actual = input.IndexOf(letter);
Assert.Equal(expected, actual);
}
}

public class IndexOfData : IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "hello world", 'w', 6 },
new object[] { "goodnight moon", 'w', -1 }
};

public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }

IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}

XUnit >= 2.0: Instead of ClassData, now there's an 'overload' of [MemberData] that allows to use static members from other classes. Examples below have been updated to use it, since XUnit < 2.x is pretty ancient now.
Another option is ClassData, which works the same, but allows to easily share the 'generators' between tests in different classes/namespaces, and also separates the 'data generators' from the actual test methods.

MemberData Example: look there to another type

public class StringTests3
{
[Theory, MemberData(nameof(IndexOfData.SplitCountData), MemberType = typeof(IndexOfData))]
public void IndexOf(string input, char letter, int expected)
{
var actual = input.IndexOf(letter);
Assert.Equal(expected, actual);
}
}

public class IndexOfData : IEnumerable<object[]>
{
public static IEnumerable<object[]> SplitCountData =>
new List<object[]>
{
new object[] { "hello world", 'w', 6 },
new object[] { "goodnight moon", 'w', -1 }
};
}

Disclaimer :)

Last time checked @20210903 with dotnetfiddle.net on C# 5.0 and xunit 2.4.1 .. and failed. I couldn't mix-in a test-runner into that fiddle. But at least it compiled fine. Note that this was originally written years ago, things changed a little. I fixed them according to my hunch and comments. So.. it may contain inobvious typos, otherwise obvious bugs that would instantly pop up at runtime, and traces of milk & nuts.

Can I pass a deligate to a Xunit Theory

public static IEnumerable<object[]> TestData()
{
Action<string> a1 = input => new ConcreteConstructor(input);
yield return new object[] { a1 };
Action<string> a2 = input => new AnotherConstructor(input);
yield return new object[] { a2 };
}

[Theory]
[MemberData(nameof(TestData))]
public void Constructor_Should_Not_Throw_Expection(Action<string> constructor)
{
constructor("SomeString");
}

How to provide List int for a data theory ? InlineData

This cannot be accomplished with InlineData you can only do this with MemberData, PropertyData or ClassData see the MemberData example below.

[MemberData(nameof(Data))]
public void VerifyGetCarListAsync(int? colorID, List<int> carIDs, int? sellerID){
// test code
}

public static IEnumerable<object[]> Data(){
yield return new object[] { null, new List<int>{ 42, 2112 }, null };
yield return new object[] { 1, new List<int>{ 43, 2112 }, null };
yield return new object[] { null, new List<int>{ 44, 2112 }, 6 };
}

In F# how do you pass a collection to xUnit's InlineData attribute

InlineDataAttribute leans on the C# params mechanism. This is what enables the default syntax of InlineData in C# :-

[InlineData(1,2)]

Your version with array construction:-

[InlineData( new object[] {1,2})]

is simply what the compiler translates the above into. The minute you go further, you'll run into the same restrictions on what the CLI will actually enable - the bottom line is that at the IL level, using attribute constructors implies that everything needs to be boiled down to constants at compile time. The F# equivalent of the above syntax is simply: [<InlineData(1,2)>], so the direct answer to your question is:

module UsingInlineData =
[<Theory>]
[<InlineData(1, 2)>]
[<InlineData(1, 1)>]
let v4 (a : int, b : int) : unit = Assert.NotEqual(a, b)

I was unable to avoid riffing on @bytebuster's example though :) If we define a helper:-

type ClassDataBase(generator : obj [] seq) = 
interface seq<obj []> with
member this.GetEnumerator() = generator.GetEnumerator()
member this.GetEnumerator() =
generator.GetEnumerator() :> System.Collections.IEnumerator

Then (if we are willing to forgo laziness), we can abuse list to avoid having to use seq / yield to win the code golf:-

type MyArrays1() = 
inherit ClassDataBase([ [| 3; 4 |]; [| 32; 42 |] ])

[<Theory>]
[<ClassData(typeof<MyArrays1>)>]
let v1 (a : int, b : int) : unit = Assert.NotEqual(a, b)

But the raw syntax of seq can be made sufficiently clean, so no real need to use it as above, instead we do:

let values : obj[] seq = 
seq {
yield [| 3; 4 |]
yield [| 32; 42 |] // in recent versions of F#, `yield` is optional in seq too
}

type ValuesAsClassData() =
inherit ClassDataBase(values)

[<Theory; ClassData(typeof<ValuesAsClassData>)>]
let v2 (a : int, b : int) : unit = Assert.NotEqual(a, b)

However, most idiomatic with xUnit v2 for me is to use straight MemberData (which is like xUnit v1's PropertyData but generalized to also work on fields) :-

[<Theory; MemberData("values")>]
let v3 (a : int, b : int) : unit = Assert.NotEqual(a, b)

The key thing to get right is to put the : seq<obj> (or : obj[] seq) on the declaration of the sequence or xUnit will throw at you.


Later versions of xUnit 2 include a typed TheoryData, which lets you write:

type Values() as this =
inherit TheoryData<int,int>()
do this.Add(3, 4)
this.Add(32, 42)

[<Theory; ClassData(typeof<Values>)>]
let v2 (a : int, b : int) : unit = Assert.NotEqual(a, b)

That also type-checks each argument.

XUnit - Mixing theory data mechanisms, input and expected data

Here's a suggestion:

  1. Create a class to generate test data:
    internal static class TestData
{
public static IList<T> Get<T>(int count = 10)
{
// I'm using NBuilder here to generate test data quickly.
// Use your own logic to create your test data.
return Builder<T>.CreateListOfSize(count).Build();
}
}

Now, all your test classes can leverage this to get the same set of test data. So, in your data class, you would do something along the lines of

public class DataClass
{
public static IEnumerable<object[]> Data()
{
return new List<object[]>
{
new object[] { TestData.Get(), this.ExpectedResult() }
};
}
}

Now you can follow through with your original approach:

[Theory]
[MemberData(nameof(DataClass.Data), MemberType = typeof(DataClass))]
public void TestValidConfig(Data input, Configuration expected)
{
...
}

If your tests don't mutate the input data, You can collect them into a fixture and inject the input data through constructor. This would speed up the tests since you don't have to generate the input data per test. Check out shared context for more information.



Related Topics



Leave a reply



Submit