

Parameterized xUnit Tests with F#
source link: https://draptik.github.io/posts/2022/01/12/fsharp-writing-parameterized-xunit-tests/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

This post showcases different ways of writing parameterized tests with xUnit using F#.
I’m assuming you
- have a basic knowledge of F#
- are familiar with the concept of parameterized tests
- are familiar with parameterized tests with C#/xUnit
TL/DR
type Somebody = { Name : string }
let samplesTLDR : obj[] list =
[
[| { Name = "Homer" }; "Homer" |]
[| { Name = "Marge" }; "Marge" |]
]
[<Theory>]
[<MemberData(nameof(samplesTLDR))>]
let ``test TLDR`` someBody expected =
Assert.Equal(expected, someBody.Name)
InlineData
The simplest way to write a parameterized test with xUnit is using the InlineData
attribute:
[<Theory>]
[<InlineData(1, 42, 43)>]
[<InlineData(1, 2, 3)>]
let ``inlinedata hello world`` (a : int) (b : int) (expected : int) =
let actual = a + b
Assert.Equal(expected, actual)
Executing this test will run the test twice: First, using the values 1, 42, and 43, and then a second time, using the values 1, 2, and 3.
The values are passed to the test method’s input parameters (a
, b
, and expected
) and can then be used inside the test. We can use as many InlineData
s as we like.
InlineData
has a limitation though: It only accepts basic data types (string, int, bool, etc).
It can’t deal with collections or custom types.
This is where xUnit’s MemberData
attribute comes into play.
MemberData - Intro
We create a function sampleNumbers
which returns a list of object arrays (obj[] list
).
BTW: obj
is an F# alias for C#’s Object
.
// Return signature: obj [] list
let sampleNumbers : obj [] list =
[
[| 1 |]
[| 2 |]
[| 3 |]
]
// Theory with MemberData: simple example
[<Theory>]
[<MemberData(nameof(sampleNumbers))>]
let ``xunit memberData hello world`` number =
Assert.True(number > 0)
This function can then be referenced by the MemberData
attribute.
In this example each object array only contains a single number, which is then passed to the test methods input parameter number
.
Sample Data
Next, we will create a custom type which we can use as input data for testing:
type Person = { Name: string; Incidents: int; Age: int }
let lisa = { Name = "Lisa"; Incidents = 0; Age = 6 }
let marge = { Name = "Marge"; Incidents = 0; Age = 39 }
let homer = { Name = "Homer"; Incidents = 10; Age = 42 }
let bart = { Name = "Bart"; Incidents = 42; Age = 8 }
MemberData - inferred return type
A simple function providing this data:
// Return type is inferred as `Person[] list`
let samplePeople =
[
[| homer |]
[| marge |]
[| lisa |]
[| bart |]
]
Note that this time we are not defining the return type and the compiler will infer Person[] list
.
This will only work because we are dealing with a single type:
[<Theory>]
[<MemberData(nameof(samplePeople))>]
let ``xunit memberData with single type`` person =
Assert.True(person.Age > 0)
MemberData - sequence instead of list
Xunit’s MemberData
also tolerates using a sequence (seq
) instead of a list
, but in this case we must yield
the result.
Since the inner collection is of type obj[]
we are free to mix different types.
// When mixing different data types (here: Person and string)
// ensure that the returned collection is `obj[] seq`
let sampleDataWithExpected : obj[] seq =
seq {
yield [| homer; "Homer" |]
yield [| marge; "Marge" |]
}
[<Theory>]
[<MemberData(nameof(sampleDataWithExpected))>]
let ``different types and return signature seq`` person name =
Assert.Equal(name, person.Name)
MemberData - inferred type with boxing
Another way of ensuring that the inner collection is of type obj[]
is to use F#’s box
function:
// No return signature -> boxing
// Only the first element of the first collection has to be boxed
let sampleData2WithExpected =
seq {
yield [| box homer; "Homer" |]
yield [| marge; "Marge" |]
}
[<Theory>]
[<MemberData(nameof(sampleData2WithExpected))>]
let ``different types and no return signature/boxing`` person name =
Assert.Equal(name, person.Name)
box
converts a strongly typed value into an obj
.
After some experimenting it seems that only the first entry of the first collection has to be box
ed.
All other values will have the same type as the first value.
MemberData - inferred type with upcasting
A similar approach to box
ing is to upcast the value to Object
using the :>
operator:
// No return signature -> casting to object
// Only the first element of the first collection has to be casted
let sampleData3WithExpected =
seq {
yield [| homer :> Object; "Homer" |]
yield [| marge; "Marge" |]
}
[<Theory>]
[<MemberData(nameof(sampleData3WithExpected))>]
let ``different types and no return signature/upcasting`` person name =
Assert.Equal(name, person.Name)
Just like box
ing, only the first entry of the first collection seems to need this.
BTW: If your first reaction is “upcasting is evil”: Same here, just showing possible options…
Summary
The shortest variant, and the most readable IMHO, is still manually defining the return type in the data generating function:
// Shortest variant
let samplePeopleWithResult : obj[] list =
[
[| homer; "Homer" |]
[| marge; "Marge" |]
[| lisa; "Lisa" |]
[| bart; "Bart" |]
]
[<Theory>]
[<MemberData(nameof(samplePeopleWithResult))>]
let ``different types and return signature list`` person name =
Assert.Equal(name, person.Name)
Resources
I found these references useful while learning about this topic:
An accompanying GitHub repository to this post can be found here: https://github.com/draptik/fsharp-xunit-parameterized-tests.
Recommend
-
8
Parameterized Tests using JUnit5
-
11
Running xUnit.net Tests on Specific Threads for WPF and Other UI Tests
-
27
Use null values in JUnit 5 parameterized tests JUnit 5 allows you to parameterize your tests so that the same test case is executed repe...
-
8
Parameterized tests Junit using the file entry advertisements I have a query about using parameterized tests for my unit testing of my APIs. Now in...
-
8
Online TrainingNON-FUNCTIONALSTART:29 September 2021Test Automation AdvancedLEVEL: 2C# Level 2
-
8
Better Parameterized Tests with BurstAn alternate data variation mechanism for JUnit tests.Written by Daniel Lubarov, Jake Wharton,...
-
27
Parameterized Tests in Cypress How to write parameterized tests in Cypress Cypress is a testing framework for anything running on a web browser....
-
8
Parameterized (data-driven) Tests in Vitest + example
-
6
Kotlin Multiplatform Parameterized Tests and Grouping Using The Standard Kotlin Testing Framework Keeping Kotlin Multiplatform tests clean while using the standard kotlin.test framework
-
3
JUnit 5 Parameterized Tests Table Of Contents If you’re reading this article, it means you’re already well-versed with JUnit. Let me give you a summary of JUnit - In software develop...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK