Install any skill in seconds. Free to start, no credit card required.
Get Started Free →F#テストフレームワーク、プロパティベーステスト、および関数型アプローチ。
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 70% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 323% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 29% | 0% |
Comprehensive testing patterns for F# applications using xUnit, FsUnit, Unquote, FsCheck, and modern .NET testing practices.
| Tool | Purpose | |---|---| | xUnit | Test framework (standard .NET ecosystem choice) | | FsUnit.xUnit | F#-friendly assertion syntax for xUnit | | Unquote | Assertion library using F# quotations for clear failure messages | | FsCheck.xUnit | Property-based testing integrated with xUnit | | NSubstitute | Mocking .NET dependencies | | Testcontainers | Real infrastructure in integration tests | | WebApplicationFactory | ASP.NET Core integration tests |
fsharpmodule 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 @>)
Unquote uses F# quotations so failure messages show the full expression that failed, not just "expected X got Y".
fsharpmodule 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 @>
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 @> }
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("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 @>
fsharpopen 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
fsharptype 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
fsharplet 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 @> }
fsharpopen 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>()) }
fsharptype 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 @> }
tests/
MyApp.Tests/
Unit/
OrderServiceTests.fs
PaymentServiceTests.fs
Integration/
OrderApiTests.fs
OrderRepositoryTests.fs
Properties/
OrderPropertyTests.fs
Helpers/
TestData.fs
TestDeps.fs| Anti-Pattern | Fix | |---|---| | Testing implementation details | Test behavior and outcomes | | Mutable shared test state | Fresh state per test | | Thread.Sleep in async tests | Use Task.Delay with timeout, or polling helpers | | Asserting on sprintf output | Assert on typed values and pattern matches | | Ignoring CancellationToken | Always pass and verify cancellation | | Skipping property-based tests | Use FsCheck for any function with clear invariants |
dotnet-patterns - Idiomatic .NET patterns, dependency injection, and architecturecsharp-testing - C# testing patterns (shared infrastructure like WebApplicationFactory and Testcontainers applies to F# too)bash# 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/
Other measured skills in the registry, with their headline benchmark lift.