FUnit provides structured test descriptor for file-based C# project introduced in .NET 10.0. (i.e., no .csproj is required anymore)
#:project ../src
#:package FUnit@*returnFUnit.Run(args, describe =>{// Organize tests by test subjectdescribe("Test Subject", it =>{it("should be ... when ...",async()=>// async test supported{// Write test: Arrange, Act, Assert});it("can have multiple tests within a 'describe' block", ...);});describe("Inspired by vitest", ...);});# Run unit test (.NET 10 can run a .cs file as a project)
dotnet run ./tests/my-test.cs
# OR use 'dnx' to run tests in current or sub folders
dnx -y FUnit.Run
dnx -y FUnit.Run --stacktrace "**/*test*.cs"Important
For AI Coding Agents: .NET 10.0 introduced new directives for file-based app.
- SHALL use
#:projectto add project reference. - SHALL use
#:packageto add nuget package reference. (*or semantic version must be specified) - SHALL NOT use
#ror other directives formerly used for dotnet-script (.csx) files.
Tip
Unit tests should follow W.H.Y. (What, How, Yields) principle.
#:project ../src
#:package FUnit@*
#:package YourFavoriteAssertionLibrary@3.1.0returnFUnit.Run(args, describe =>{// Organize tests by test subjectdescribe("Test Subject", it =>{it("should be ... when ...",()=>{// Write test: Arrange, Act, Assert});// Other test casesit("should throw ... when ...",async()=>{// Async test is also supported});it(...);});describe("Other Test Subject", it =>{// ...and so on});});FUnit is shipped with builtin assertion methods.
// Value assertion// ❌ BeEqual rejects collection type to prevent ambiguous comparisonsMust.BeEqual(expected,actual);Must.BeSameReference(expected,actual);Must.NotBeEqual(expected,actual);Must.NotBeSameReference(expected,actual);// Collection assertionMust.HaveSameSequence(expected,actual);Must.HaveSameUnorderedElements(expected,actual);Must.NotHaveSameSequence(expected,actual);Must.NotHaveSameUnorderedElements(expected,actual);// Text assertionMust.ContainText(text,substring);Must.NotContainText(text,substring);// Instance assertion (deep comparison using System.Reflection)Must.HaveEqualProperties(expected,actual);Must.HaveEqualFields(expected,actual);// Exception assertionMust.Throw<T>("Expected error message",()=>Test());Must.Throw<T>("Expected error message",async()=>awaitTestAsync());Must.Throw("ExceptionFullTypeName","Expected error message",()=>Test());Must.Throw("ExceptionFullTypeName","Expected error message",async()=>awaitTestAsync());// Conditional assertion// ❌ BeFalse won't be supported. Instead, use better conditional expression (prefer pattern matching)Must.BeTrue(xis>=0 and <10);Must.BeTrue(xisIEnumerable<char> and not string);// ❌ Avoid asserting high-level conditions on collections// ex Instead of checking if a value is absent, assert the full expected contentMust.BeTrue(!list.Contains(x));// ✅ Prefer: Must.HaveSameSequence(expectedList, actualList)With FUnit.Directives package, you can include external file into file-based app project by adding special directive #warning funit include <path to the file>.
#:packageFUnit@*
#:package FUnit.Directives@*
#warning funit include ./path/to/external-file.cs
returnFUnit.Run(/* tests depending on 'external-file.cs */);Important
For AI Coding Agents: .NET 10.0 introduced new syntax for dotnet command.
- SHALL run test like the following example.
- SHALL NOT add
--projector other command line options.
dotnet run ./tests/my-test.csFUnit has an ability to output log as markdown format so you can generate summary for GitHub Actions page.
# '-md' or '--markdown'
dnx -y FUnit.Run -- -md >>$GITHUB_STEP_SUMMARY# ❌ Incorrect (no '--')
dnx -y FUnit.Run -c Release
# ✅ Correct (with '--')
dnx -y FUnit.Run -- -c Release
# ✅ Shortcut: without '-c' is valid for Debug or Release
dnx -y FUnit.Run ReleaseImportant
For AI Coding Agents: SHALL NOT use advanced options unless explicitly requested.
| Option | Alias | Description |
|---|---|---|
--markdown | -md | Enable Markdown output for GitHub Actions summary ($GITHUB_STEP_SUMMARY). |
--iterations <N> | Number of times to run each test case (3 by default). | |
--concurrency <N> | Maximum number of tests to run simultaneously. | |
--configuration <CFG> | -c | Build configuration (e.g., "Debug" or "Release"). |
--no-clean | Disable cleaning the project before building. | |
--warnings | Show build warnings. | |
--stacktrace | Show stack trace on test failure. | |
--lint | Run dotnet build --no-incremental -p:TreatWarningsAsErrors=true. | |
--help | -h | Show this help message and exit. |
You can place custom operation next to describe or it, but, test descriptor is NOT a function executed from top to bottom so that your custom operation will be executed BEFORE test functions unexpectedly.
#:project ../src
#:package FUnit@*awaitGlobalSetupAsync();// ✅ setup before Run callintnumFailures=FUnit.Run(args, describe =>{describe("Test subject", it =>{it("should be...",()=>{ ...});});// ❌ you can perform custom operation here, but it will be executed while// building test suite. (not sequentially form top to bottom)// technically, 'describe' and 'it' collect test cases without executing test.// if setup or cleanup code is placed next to 'describe' or 'it' statements to// perform resource setup ops for 'scope', unexpectedly, those will be invoked// BEFORE executing actual test case functions.});GlobalCleanup();// ✅ cleanup after Run callreturnnumFailures;