Fsharp Testing

by affaan-mef648e01899bNo license275K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 3 days ago

F#テストフレームワーク、プロパティベーステスト、および関数型アプローチ。

Instructions onlySoftware Development
AI-generated overview

Guides writing and reviewing F# tests with xUnit, FsUnit, Unquote, FsCheck, and .NET integration testing.

What it does
This skill provides F# testing patterns covering unit tests with xUnit and FsUnit, quotation-based assertions with Unquote, property-based testing with FsCheck, mocking via function stubs or NSubstitute, and ASP.NET Core integration tests with WebApplicationFactory. It also suggests a test folder layout, lists common anti-patterns with fixes, and gives dotnet test commands for running, filtering, and collecting coverage.
When to use it
Use it when writing new tests for F# code, reviewing test quality and coverage, setting up test infrastructure for an F# project, or debugging flaky or slow tests.
Requirements
No scripts are included; it is instructions only. The described patterns assume a .NET toolchain with dotnet test and packages such as xUnit, FsUnit.xUnit, Unquote, FsCheck.xUnit, NSubstitute, Testcontainers, and WebApplicationFactory.

F# Testing Patterns

Comprehensive testing patterns for F# applications using xUnit, FsUnit, Unquote, FsCheck, and modern .NET testing practices.

When to Activate

  • Writing new tests for F# code
  • Reviewing test quality and coverage
  • Setting up test infrastructure for F# projects
  • Debugging flaky or slow tests

Test Framework Stack

ToolPurpose
xUnitTest framework (standard .NET ecosystem choice)
FsUnit.xUnitF#-friendly assertion syntax for xUnit
UnquoteAssertion library using F# quotations for clear failure messages
FsCheck.xUnitProperty-based testing integrated with xUnit
NSubstituteMocking .NET dependencies
TestcontainersReal infrastructure in integration tests
WebApplicationFactoryASP.NET Core integration tests

Unit Tests with xUnit + FsUnit

Basic Test Structure

fsharp
module OrderServiceTests
open Xunitopen FsUnit.Xunit
[<Fact>]let ``create sets status to Pending`` () =    let order = Order.create "cust-1" [ validItem ]    order.Status |> should equal Pending
[<Fact>]let ``confirm changes status to Confirmed`` () =    let order = Order.create "cust-1" [ validItem ]    let confirmed = Order.confirm order    confirmed.Status |> should be (ofCase <@ Confirmed @>)

Assertions with Unquote

Unquote uses F# quotations so failure messages show the full expression that failed, not just "expected X got Y".

fsharp
module OrderValidationTests
open Xunitopen Swensen.Unquote
[<Fact>]let ``PlaceOrder returns success when request is valid`` () =    let request = { CustomerId = "cust-123"; Items = [ validItem ] }    let result = OrderService.placeOrder request    test <@ Result.isOk result @>
[<Fact>]let ``order total sums item prices`` () =    let items = [ { Sku = "A"; Quantity = 2; Price = 10m }                  { Sku = "B"; Quantity = 1; Price = 5m } ]    let total = Order.calculateTotal items    test <@ total = 25m @>
[<Fact>]let ``validated email rejects empty input`` () =    let result = ValidatedEmail.create ""    test <@ Result.isError result @>

Async Tests

fsharp
[<Fact>]let ``PlaceOrder returns success when request is valid`` () = task {    let deps = createTestDeps ()    let request = { CustomerId = "cust-123"; Items = [ validItem ] }
    let! result = OrderService.placeOrder deps request
    test <@ Result.isOk result @>}
[<Fact>]let ``PlaceOrder returns error when items are empty`` () = task {    let deps = createTestDeps ()    let request = { CustomerId = "cust-123"; Items = [] }
    let! result = OrderService.placeOrder deps request
    test <@ Result.isError result @>}

Parameterized Tests with Theory

fsharp
[<Theory>][<InlineData("")>][<InlineData("   ")>]let ``PlaceOrder rejects empty customer ID`` (customerId: string) =    let request = { CustomerId = customerId; Items = [ validItem ] }    let result = OrderService.placeOrder request    result |> should be (ofCase <@ Error @>)
[<Theory>][<InlineData("", false)>][<InlineData("a", false)>][<InlineData("[email protected]", true)>][<InlineData("[email protected]", true)>]let ``IsValidEmail returns expected result`` (email: string, expected: bool) =    test <@ EmailValidator.isValid email = expected @>

Property-Based Testing with FsCheck

Using FsCheck.xUnit

fsharp
open FsCheckopen FsCheck.Xunit
[<Property>]let ``order total is always non-negative`` (items: NonEmptyList<PositiveInt * decimal>) =    let orderItems =        items.Get        |> List.map (fun (qty, price) ->            { Sku = "SKU"; Quantity = qty.Get; Price = abs price })    let total = Order.calculateTotal orderItems    total >= 0m
[<Property>]let ``serialization roundtrips`` (order: Order) =    let json = JsonSerializer.Serialize order    let deserialized = JsonSerializer.Deserialize<Order> json    deserialized = order

Custom Generators

fsharp
type OrderGenerators =    static member ValidEmail () =        gen {            let! user = Gen.elements [ "alice"; "bob"; "carol" ]            let! domain = Gen.elements [ "example.com"; "test.org" ]            return $"{user}@{domain}"        }        |> Arb.fromGen
[<Property(Arbitrary = [| typeof<OrderGenerators> |])>]let ``valid emails pass validation`` (email: string) =    EmailValidator.isValid email

Mocking Dependencies

Function Stubs (Preferred)

fsharp
let createTestDeps () =    let mutable savedOrders = []    { FindOrder = fun id -> task { return Map.tryFind id testData }      SaveOrder = fun order -> task { savedOrders <- order :: savedOrders }      SendNotification = fun _ -> Task.CompletedTask }
[<Fact>]let ``PlaceOrder saves the confirmed order`` () = task {    let mutable saved = []    let deps =        { createTestDeps () with            SaveOrder = fun order -> task { saved <- order :: saved } }
    let! _ = OrderService.placeOrder deps validRequest
    test <@ saved.Length = 1 @>}

NSubstitute for .NET Interfaces

fsharp
open NSubstitute
[<Fact>]let ``calls repository with correct ID`` () = task {    let repo = Substitute.For<IOrderRepository>()    repo.FindByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())        .Returns(Task.FromResult(Some testOrder))
    let service = OrderService(repo)    let! _ = service.GetOrder(testOrder.Id, CancellationToken.None)
    do! repo.Received(1).FindByIdAsync(testOrder.Id, Arg.Any<CancellationToken>())}

ASP.NET Core Integration Tests

fsharp
type OrderApiTests (factory: WebApplicationFactory<Program>) =    interface IClassFixture<WebApplicationFactory<Program>>
    let client =        factory.WithWebHostBuilder(fun builder ->            builder.ConfigureServices(fun services ->                services.RemoveAll<DbContextOptions<AppDbContext>>() |> ignore                services.AddDbContext<AppDbContext>(fun options ->                    options.UseInMemoryDatabase("TestDb") |> ignore) |> ignore))            .CreateClient()
    [<Fact>]    member _.``GET order returns 404 when not found`` () = task {        let! response = client.GetAsync($"/api/orders/{Guid.NewGuid()}")        test <@ response.StatusCode = HttpStatusCode.NotFound @>    }

Test Organization

tests/  MyApp.Tests/    Unit/      OrderServiceTests.fs      PaymentServiceTests.fs    Integration/      OrderApiTests.fs      OrderRepositoryTests.fs    Properties/      OrderPropertyTests.fs    Helpers/      TestData.fs      TestDeps.fs

Common Anti-Patterns

Anti-PatternFix
Testing implementation detailsTest behavior and outcomes
Mutable shared test stateFresh state per test
Thread.Sleep in async testsUse Task.Delay with timeout, or polling helpers
Asserting on sprintf outputAssert on typed values and pattern matches
Ignoring CancellationTokenAlways pass and verify cancellation
Skipping property-based testsUse FsCheck for any function with clear invariants

Related Skills

  • dotnet-patterns - Idiomatic .NET patterns, dependency injection, and architecture
  • csharp-testing - C# testing patterns (shared infrastructure like WebApplicationFactory and Testcontainers applies to F# too)

Running Tests

bash
# Run all testsdotnet test
# Run with coveragedotnet test --collect:"XPlat Code Coverage"
# Run specific projectdotnet test tests/MyApp.Tests/
# Filter by test namedotnet test --filter "FullyQualifiedName~OrderService"
# Watch mode during developmentdotnet watch test --project tests/MyApp.Tests/

Source and attribution

Source:affaan-m/eccindocs/ja-JP/skills/fsharp-testingat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

Fsharp Testing Agent Skill | SourceWeft