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.

Tags:

.Net

Tdd

F#

Xunit