Skip to content

F# Guide — Testing: Unit and Property-Based Testing

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

F# testing uses standard .NET frameworks like xUnit and NUnit for unit tests plus FsCheck for property-based testing, taking advantage of pure functions and immutability for naturally testable code.

What You'll Learn

  • xUnit unit testing in F#
  • FsCheck property-based testing
  • Test-driven development in F#
  • Mocking and test doubles
  • Integration testing patterns

Why It Matters

F#'s functional nature makes testing easier. Pure functions always return the same output for the same input, making tests deterministic.

Real-World Use

Financial systems use property-based testing for calculation invariants. Web services use integration tests for API validation.

flowchart LR
    A["Testing"] --> B["xUnit"]
    B --> C["FsCheck"]
    C --> D["TDD"]
    D --> E["Integration"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

xUnit Setup

// Add xUnit and FsUnit packages
// dotnet add package xunit
// dotnet add package FsUnit

module Tests

open Xunit
open FsUnit

[<Fact>]
let ``Adding 1 and 2 equals 3`` () =
    let result = add 1 2
    result |> should equal 3

Test Organization

module MathTests

open Xunit

let add x y = x + y

[<Fact>]
let ``add returns sum of two numbers`` () =
    let result = add 3 5
    Assert.Equal(8, result)

[<Theory>]
[<InlineData(1, 2, 3)>]
[<InlineData(-1, 1, 0)>]
[<InlineData(0, 0, 0)>]
let ``add with various inputs`` (a: int, b: int, expected: int) =
    let result = add a b
    Assert.Equal(expected, result)

FsCheck Property Tests

open FsCheck
open FsCheck.Xunit

[<Property>]
let ``Reversing a list twice returns the original`` (xs: int list) =
    List.rev (List.rev xs) = xs

[<Property>]
let ``Sorting a list makes it non-decreasing`` (xs: int list) =
    let sorted = List.sort xs
    sorted = List.sort sorted

Custom Generators

open FsCheck

// Custom generator for positive numbers
let positiveInt =
    Arb.generate<int>
    |> Gen.filter (fun x -> x > 0)

type PositiveInt =
    static member Int() =
        Arb.fromGen positiveInt

[<Property(Arbitrary = [| typeof<PositiveInt> |])>]
let ``Division by positive number never fails`` (x: int) (y: int) =
    y > 0 ==> lazy (try x / y |> ignore; true with _ -> false)

Test Fixtures

type DatabaseFixture() =
    let connection = openConnection()

    member this.Connection = connection

    interface System.IDisposable with
        member this.Dispose() = closeConnection connection

[<CollectionDefinition("Database")>]
type DatabaseCollection() =
    interface ICollectionFixture<DatabaseFixture>

Mocking

// F# rarely needs mocks - pass functions instead
type IRepository =
    abstract GetUser: int -> User option

let testRepository getUserFunc =
    { new IRepository with
        member _.GetUser(id) = getUserFunc id }

let repo = testRepository (fun id -> Some { Name = "Test" })

Integration Tests

module IntegrationTests

open Xunit
open System.Net.Http

[<Fact>]
let ``API health check returns OK`` () = task {
    use client = new HttpClient()
    let! response = client.GetAsync("http://localhost:5000/health")
    Assert.True(response.IsSuccessStatusCode)
}

Common Mistakes

1. Testing implementation details

Test behavior, not implementation. Pure functions make this natural.

2. Not using property tests

Property-based testing finds edge cases example-based tests miss. Use both.

3. Over-mocking

Functional F# rarely needs mocks. Pass functions as parameters instead.

4. Ignoring FsUnit

FsUnit provides F#-idiomatic assertions like should equal. More readable than Assert.Equal.

5. Slow test suites

Keep unit tests fast. Move slow integration tests to a separate test project.

Practice Questions

1. What is the difference between [] and []? [] is a single test. [] runs the same test with multiple input values.

2. What is property-based testing? Testing that verifies invariants (properties) hold across many randomly generated inputs.

3. Why are F# functions naturally testable? Pure functions always return the same output for the same input, with no hidden state.

Challenge: Write property-based tests for a custom sorting function verifying idempotence, order, and element preservation.

FAQ

{{< faq question="What testing framework should I use?" >} xUnit is the most popular. NUnit also works well. Both support F# idiomatic testing patterns. {{< /faq >}}

{{< faq question="Do I need mocking frameworks?" >} Rarely in F#. Use function parameters for dependency injection instead of mocking interfaces. {{< /faq >}}

{{< faq question="How do I test async code?" >} Use task { } or async { } in tests. xUnit supports async test methods returning Task. {{< /faq >}}

{{< faq question="What is FsCheck?" >} A property-based testing framework inspired by Haskell's QuickCheck. It generates random test cases and shrinks failures. {{< /faq >}}

{{< faq question="How do I measure Code Coverage?" >} Use Coverlet or dotCover with your test runner. Run dotnet test --collect:"XPlat Code Coverage". {{< /faq >}}

Mini Project

Build a test suite for a simple calculator:

module Calculator

let add x y = x + y
let subtract x y = x - y
let multiply x y = x * y
let divide x y = if y = 0 then None else Some (x / y)

module Tests

open Xunit
open FsCheck.Xunit

[<Fact>]
let ``add adds two numbers`` () =
    Assert.Equal(5, add 2 3)

[<Property>]
let ``add is commutative`` (a: int) (b: int) =
    add a b = add b a

[<Property>]
let ``add is associative`` (a: int) (b: int) (c: int) =
    add (add a b) c = add a (add b c)

[<Property>]
let ``divide by zero returns None`` (x: int) =
    divide x 0 = None

What's Next

Now that you understand testing, explore .NET interop for calling C# code.

Topic Description Link
F# .NET Interop C# interop {{< ref "27-net-interop" >}}
F# Fable F# to JavaScript {{< ref "28-fable" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro