Source profileQuality 77/100

affaan-m/ECC/skills/fsharp-testing/SKILL.md

fsharp-testing

F# testing patterns with xUnit, FsUnit, Unquote, FsCheck property-based testing, integration tests, and test organization best practices.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

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

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/fsharp-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "fsharp-testing" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/fsharp-testing/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      When to Activate

      Writing new tests for F code

      Writing new tests for F codeReviewing test quality and coverageSetting up test infrastructure for F projects
    2. 02

      Test Framework Stack

      Review the “Test Framework Stack” section in the pinned source before continuing.

      Review and apply the “Test Framework Stack” source section.
    3. 03

      Unit Tests with xUnit + FsUnit

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

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

      Basic Test Structure

      Review the “Basic Test Structure” section in the pinned source before continuing.

      Review and apply the “Basic Test Structure” source section.
    5. 05

      Assertions with Unquote

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

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

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score77/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/fsharp-testing/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    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

    module OrderServiceTests
    
    open Xunit
    open 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".

    module OrderValidationTests
    
    open Xunit
    open 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

    [<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

    [<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("user@example.com", true)>]
    [<InlineData("user+tag@example.co.uk", true)>]
    let ``IsValidEmail returns expected result`` (email: string, expected: bool) =
        test <@ EmailValidator.isValid email = expected @>
    

    Property-Based Testing with FsCheck

    Using FsCheck.xUnit

    open FsCheck
    open 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

    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)

    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

    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

    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

    # Run all tests
    dotnet test
    
    # Run with coverage
    dotnet test --collect:"XPlat Code Coverage"
    
    # Run specific project
    dotnet test tests/MyApp.Tests/
    
    # Filter by test name
    dotnet test --filter "FullyQualifiedName~OrderService"
    
    # Watch mode during development
    dotnet watch test --project tests/MyApp.Tests/
    

    Alternatives

    Compare before choosing