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.
As described in this question, you can only use literals with InlineData
. Lists are not literals.
However, xUnit provides with ClassData
which seems to do what you need.
This question discusses the same problem for C#.
In order to use ClassData
with the tests, just make a data class implementing seq<obj[]>
:
type MyArrays () =
let values : seq<obj[]> =
seq {
yield [|3; 4|] // 1st test case
yield [|32; 42|] // 2nd test case, etc.
}
interface seq<obj[]> with
member this.GetEnumerator () = values.GetEnumerator()
member this.GetEnumerator () =
values.GetEnumerator() :> System.Collections.IEnumerator
module Theories =
[<Theory>]
[<ClassData(typeof<MyArrays1>)>]
let ``given an array it should be able to pass it to the test`` (a : int, b : int) : unit =
Assert.NotEqual(a, b)
Albeit this requires some manual coding, you may re-use the data class, which appears to be useful in real-life projects, where we often run different tests against the same data.
You can use the FSharp.Reflection
namespace to good effect here. Consider some hypothetical function isAnswer : (string -> int -> bool)
that you want to test with a few examples.
Here's one way:
open FSharp.Reflection
open Xunit
type TestData() =
static member MyTestData =
[ ("smallest prime?", 2, true)
("how many roads must a man walk down?", 41, false)
] |> Seq.map FSharpValue.GetTupleFields
[<Theory; MemberData("MyTestData", MemberType=typeof<TestData>)>]
let myTest (q, a, expected) =
Assert.Equals(isAnswer q a, expected)
The key thing is the |> Seq.map FSharpValue.GetTupleFields
line.
It takes the list of tuples (you have to use tuples to allow different arguments types) and transforms it to the IEnumerable<obj[]>
that XUnit expects.