I would like to be about to use a list, array, and/or seq as a parameter to xUnit's InlineData.
In C# I can do this:
using Xunit; //2.1.0
namespace CsTests
{
public class Tests
{
[Theory]
[InlineData(new[] {1, 2})]
public void GivenCollectionItMustPassItToTest(int[] coll)
{
Assert.Equal(coll, coll);
}
}
}
In F# I have this:
namespace XunitTests
module Tests =
open Xunit //2.1.0
[<Theory>]
[<InlineData(8)>]
[<InlineData(42)>]
let ``given a value it must give it to the test`` (value : int) =
Assert.Equal(value, value)
[<Theory>]
[<InlineData([1; 2])>]
let ``given a list it should be able to pass it to the test``
(coll : int list) =
Assert.Equal<int list>(coll, coll)
[<Theory>]
[<InlineData([|3; 4|])>]
let ``given an array it should be able to pass it to the test``
(coll : int array) =
Assert.Equal<int array>(coll, coll)
The F# code give the following build errors:
Library1.fs (13, 16): This is not a valid constant expression or custom attribute value
Library1.fs (18, 16): This is not a valid constant expression or custom attribute value
Referring to the 2nd and 3rd test theories.
Is it possible to use xUnit to pass in collections to the 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.
Current xUnit v2 releases let you replace the Add
with constructor calls, e.g.:
type Values() =
inherit TheoryData<_, _>([
3, 4
32, 42 ])
[<Theory; ClassData(typeof<Values>)>]
let v2 (a : int, b : int) : unit = Assert.NotEqual(a, b)