{"id":24182612,"url":"https://github.com/dsschneidermann/fscheckcsharp","last_synced_at":"2026-01-04T10:04:55.896Z","repository":{"id":49122800,"uuid":"198299877","full_name":"dsschneidermann/FsCheckCSharp","owner":"dsschneidermann","description":"C# support for FsCheck","archived":false,"fork":false,"pushed_at":"2021-06-28T04:52:58.000Z","size":35,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-13T08:13:55.262Z","etag":null,"topics":["csharp","fscheck"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dsschneidermann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-22T20:48:11.000Z","updated_at":"2022-02-09T14:57:53.000Z","dependencies_parsed_at":"2022-09-05T03:00:17.492Z","dependency_job_id":null,"html_url":"https://github.com/dsschneidermann/FsCheckCSharp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsschneidermann%2FFsCheckCSharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsschneidermann%2FFsCheckCSharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsschneidermann%2FFsCheckCSharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsschneidermann%2FFsCheckCSharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dsschneidermann","download_url":"https://codeload.github.com/dsschneidermann/FsCheckCSharp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233711034,"owners_count":18717952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","fscheck"],"created_at":"2025-01-13T08:13:57.787Z","updated_at":"2025-09-21T04:31:48.785Z","avatar_url":"https://github.com/dsschneidermann.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FsCheckCSharp [![Build Status](https://img.shields.io/appveyor/ci/dsschneidermann/fscheckcsharp/master?logo=appveyor)](https://ci.appveyor.com/project/dsschneidermann/fscheckcsharp/branch/master) ![NuGet Version](https://img.shields.io/nuget/v/FsCheckCSharp) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github\u0026repo=dsschneidermann/FsCheckCSharp)](https://dependabot.com)\n\nBetter C# support for FsCheck.\n\n### Features\n\n* Configuration that supports all FsCheck options\n* Output of generated data as C# notation: object or constructor calls\n* Instrument test executions to output timing information\n\n### Getting started\n\nInstall the [FsCheckCSharp](https://nuget.org/packages/fscheckcsharp) package from NuGet:\n\n```shell\ndotnet add package FsCheckCSharp\n```\n\nIn your test file, add a using statement to access the new extension methods:\n\n```csharp\nusing FsCheckCSharp;\n```\n\nHere is an example test case:\n\n```csharp\n[Fact(Timeout = 120_000)] // xUnit is used in this example\npublic void fscheck_many_devices()\n{\n    Prop.ForAll(\n            // See full sample for how to make a Generator\n            new Generator(NumDevices: 100, NumProperties: 4, MaxRandomMs: 5000).Array(),\n                input =\u003e {\n                    // Input was generated, convert it to our actual data type\n                    var data = TelemetryGen.CreateTelemetry(input, DateTimeOffset.Now);\n\n                    // Call method under test\n                    var results = _MethodUnderTest(data).ToList();\n\n                    // Asserts\n                    TestHelpers.TelemetryIsSuperset(data, results);\n                    TestHelpers.TelemetryCountEqual(data, results);\n                }\n        )\n        .QuickCheckThrowOnFailure(CSharpNotationConfig.Default, StartSize: 100);\n        // ^ use overload from FsCheckCSharp\n}\n```\n\n### Configuration options\n\nUse the extension methods from FsCheckCSharp:\n```csharp\nProp.ForAll(/*...*/).QuickCheckThrowOnFailure(CSharpNotationConfig.Default)\nProp.ForAll(/*...*/).VerboseCheckThrowOnFailure(CSharpNotationConfig.Default)\n```\n\n* **CSharpNotationConfig** is the configuration of how output from tests should be formatted. Options are:\n\n```csharp\nCSharpNotationConfig.Default\n    .IncludeFullTypeNames() // use full type names in output\n    .PreferObjectInitialization() // always use \"new\" even if constructor can be used\n    .IncludeParameterNames() // use parameter names in constructor\n    .SkipCreateAssignment(); // skip \"var data = \"\n```\n\nIt is **recommended to pass the** `CSharpNotationConfig.Default` **always**, otherwise you might be calling the FsCheck extension method which takes no parameters.\n\nSample output with just `IncludeParameterNames`:\n```\nSystem.Exception : Falsifiable, after 4 tests (2 shrinks)\nLast step was invoked with size of 100\nShrunk:\nvar data = new[] {\n  new TelemetryGen(NegMs: 2800, DevId: 1, PropId: 1),\n  new TelemetryGen(NegMs: 1400, DevId: 1, PropId: 3),\n  new TelemetryGen(NegMs: 2799, DevId: 1, PropId: 3)\n};\nwith exception:\nXUnitTests.Infrastructure.Error: TelemetryIsSupersetAndPreservesOrder failed\n```\n\n* **FsCheck.Config** options can be specified by assigning parameters, such as:\n```csharp\n.QuickCheckThrowOnFailure(CSharpNotationConfig.Default,\n    StartSize: 100, EndSize: 1000, MaxTest: 100, MaxRejected: 10, Arbitrary: /* etc. */);\n```\n\nIt's also possible to pass an `FsCheck.Config` instance and to use additional extension methods that don't have the throw on failure behavior (`Check`/`Quick` and `Verbose`), but using the methods like in the example above is recommended.\n\n* **FsCheckRunnerConfig** is not intended to be given as it is created based on the method call, eg. `Verbose` will include information on runs, etc. Examining the source code will be necessary to figure the options out.\n\n### Full sample for FsCheck\n\nFsCheck can be hard to get started with, so sample code is provided as an example [here](https://github.com/dsschneidermann/FsCheckCSharp/tree/master/samples/FsCheckTests.cs). It is not the only way of writing a generator for FsCheck but it is my preferred approach.\n\nThe goal of the sample is to have a class that we can pass some configuration and generate some data with:\n\n```csharp\nnew Generator(NumDevices: 100, NumProperties: 4, MaxRandomMs: 5000).Array()\n```\n\nBy not specifying a size of the generated array, we defer to the FsCheck configurations `StartSize` and `EndSize` that we should set when we run the test.\n\nWe create a custom type that represents the *minimum amount of fields we need to generate* (ie. don't use your target implementation type here). In the sample, this is `TelemetryGen`. It has three properties that are generated like so:\n\n```csharp\nvar gen =\n    from negMs in ChooseInt(0, MaxRandomMs)\n    from deviceId in ChooseInt(1, NumDevices)\n    from propertyId in ChooseInt(1, NumProperties)\n    select new TelemetryGen(negMs, deviceId, propertyId);\n```\n\nIn the `Generator` class we define methods that return `FsCheck.Arbitrary\u003cT\u003e` instances. In the sample code, `Array()` and `Telemetry(...)` is defined as data that can be created. \n\nFinally, we create a method to convert the generated data into our target type. In the sample the target type is named `datatypes.TelemetryType` and the convertion is done by using an increasing timestamp and varying it by the `NegMs` value.\n\n```csharp\npublic static TelemetryType[] CreateTelemetry(\n    TelemetryGen[] input, DateTimeOffset startTime, int millisecondIncrements = 200)\n{\n    return input.Select(\n            (item, idx) =\u003e {\n                // item has DevId, PropId and NegMs\n                var enqueuedTime = startTime.AddMilliseconds((idx + 1) * millisecondIncrements);\n                return TelemetryType.CreateNumeric(\n                    // Create strings from DevId and PropId\n                    DeviceId: $\"dev{item.DevId}\", Property: $\"prop{item.PropId}\",\n                    // Use NegMs to influence the Time but not the EnqueuedTime\n                    EnqueuedTime: enqueuedTime,\n                    Time: enqueuedTime.AddMilliseconds(item.NegMs), \n                    NumericValue: 42\n                ); // \u003c- Now we have an actual TelemetryType data\n            }\n        )\n        .ToArray();\n}\n```\n\nSee the full sample file [here](https://github.com/dsschneidermann/FsCheckCSharp/tree/master/samples/FsCheckTests.cs).\n\n### Diagnostic traces on runs\n\nTests can be instrumented to produce output by adding the following:\n\n```csharp\n[Fact(Timeout = 120_000)]\npublic void fscheck_single_device_mixed_sessions()\n{\n    // Get instrumentation methods to call:\n    var (generated, tested, timer) = FsCheckRunnerConfig.TraceDiagnostics.Events;\n\n    Prop.ForAll(\n            new Generator(1, 4, 1000).Array(), input =\u003e {\n                var data = TelemetryGen.CreateTelemetry(input, Now, 1000);\n                generated(); // \u003c- call after data generation\n\n                var results = _MethodUnderTest(data).ToList();\n                timer(); // \u003c- call after your test (or as many times as you want)\n\n                TestHelpers.TelemetryIsSupersetAndPreservesOrder(data, results);\n                TestHelpers.TelemetryCountEqual(data, results);\n                tested(); // \u003c- call after asserts\n            }\n        )\n        .QuickCheckThrowOnFailure(CSharpNotationConfig.Default, \n            FsCheckRunnerConfig.TraceDiagnostics, StartSize: 100);\n        //  ^ Pass the trace diagnostics runner config\n}\n```\n\nThe methods will trace the test executions and with `VerboseCheckThrowOnFailure`, the output will include timing information on how long it takes to complete each step. With this we can determine if we have performance issues in the generator, shrinker or the method under test.\n\nIt will include output both in the test result (on failure) and will also write during the run to `Console.WriteLine`. To change the writer, specify `TraceDiagnosticsWriter`:\n```csharp\nFsCheckCSharpRunner.TraceDiagnostics.With(TraceDiagnosticsWriter: (s) =\u003e Log.Information(s))`\n```\n\nThe output will look like this, in my case, I have a dependency induced delay on the first execution of the test method:\n```\nwith trace messages:\ntest 1 / 100 -\u003e generated in 518ms\ntime passed in total is now 1,019ms\ntime passed in total is now 2,026ms\n--- 2 seconds have passed since any activity\ntime passed in total is now 3,030ms\n--- 3 seconds have passed since any activity\ntime passed in total is now 4,030ms\n--- 4 seconds have passed since any activity\ntime passed in total is now 5,024ms\n--- 5 seconds have passed since any activity\ntime passed in total is now 6,028ms\n--- 6 seconds have passed since any activity\ntime passed in total is now 7,025ms\n--- 7 seconds have passed since any activity\ntime passed in total is now 8,016ms\n--- 8 seconds have passed since any activity\ntime passed in total is now 9,058ms\n--- 9 seconds have passed since any activity\ntest 1 / 100 -\u003e timer1 hit in 8,604ms\ntest 1 / 100 -\u003e succeeded test in 95ms\ntest 2 / 100 -\u003e generated in 56ms\ntest 2 / 100 -\u003e timer1 hit in 32ms\ntest 2 / 100 -\u003e succeeded test in 0ms\ntest 3 / 100 -\u003e generated in 1ms\ntest 3 / 100 -\u003e timer1 hit in 34ms\ntest 3 / 100 -\u003e succeeded test in 1ms\ntest 4 / 100 -\u003e generated in 0ms\ntest 4 / 100 -\u003e timer1 hit in 69ms\ntest 4 / 100 -\u003e succeeded test in 0ms\n... etc\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsschneidermann%2Ffscheckcsharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdsschneidermann%2Ffscheckcsharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsschneidermann%2Ffscheckcsharp/lists"}