{"id":17792163,"url":"https://github.com/hlaueriksson/lofuunit","last_synced_at":"2026-02-27T08:04:29.456Z","repository":{"id":50121758,"uuid":"166074545","full_name":"hlaueriksson/LoFuUnit","owner":"hlaueriksson","description":"Unit Testing with Local Functions :tiger:","archived":false,"fork":false,"pushed_at":"2023-12-29T17:17:47.000Z","size":773,"stargazers_count":28,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-16T00:29:57.895Z","etag":null,"topics":["bdd","csharp","dotnet","tdd","test","testing","unittest","unittesting"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hlaueriksson.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-16T16:39:35.000Z","updated_at":"2024-07-13T21:27:35.106Z","dependencies_parsed_at":"2024-07-13T21:27:24.883Z","dependency_job_id":null,"html_url":"https://github.com/hlaueriksson/LoFuUnit","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlaueriksson%2FLoFuUnit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlaueriksson%2FLoFuUnit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlaueriksson%2FLoFuUnit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlaueriksson%2FLoFuUnit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hlaueriksson","download_url":"https://codeload.github.com/hlaueriksson/LoFuUnit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221666275,"owners_count":16860394,"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":["bdd","csharp","dotnet","tdd","test","testing","unittest","unittesting"],"created_at":"2024-10-27T10:58:12.947Z","updated_at":"2026-02-27T08:04:29.410Z","avatar_url":"https://github.com/hlaueriksson.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LoFuUnit\u003c!-- omit in toc --\u003e\n\n[![build](https://github.com/hlaueriksson/LoFuUnit/actions/workflows/build.yml/badge.svg)](https://github.com/hlaueriksson/LoFuUnit/actions/workflows/build.yml)\n[![CodeFactor](https://www.codefactor.io/repository/github/hlaueriksson/lofuunit/badge)](https://www.codefactor.io/repository/github/hlaueriksson/lofuunit)\n\n[![LoFuUnit](https://img.shields.io/nuget/v/LoFuUnit.svg?label=LoFuUnit)](https://www.nuget.org/packages/LoFuUnit)\n[![LoFuUnit.MSTest](https://img.shields.io/nuget/v/LoFuUnit.MSTest.svg?label=LoFuUnit.MSTest)](https://www.nuget.org/packages/LoFuUnit.MSTest)\n[![LoFuUnit.NUnit](https://img.shields.io/nuget/v/LoFuUnit.NUnit.svg?label=LoFuUnit.NUnit)](https://www.nuget.org/packages/LoFuUnit.NUnit)\n[![LoFuUnit.Xunit](https://img.shields.io/nuget/v/LoFuUnit.Xunit.svg?label=LoFuUnit.Xunit)](https://www.nuget.org/packages/LoFuUnit.Xunit)\n\n[![LoFuUnit.AutoFakeItEasy](https://img.shields.io/nuget/v/LoFuUnit.AutoFakeItEasy.svg?label=LoFuUnit.AutoFakeItEasy)](https://www.nuget.org/packages/LoFuUnit.AutoFakeItEasy)\n[![LoFuUnit.AutoMoq](https://img.shields.io/nuget/v/LoFuUnit.AutoMoq.svg?label=LoFuUnit.AutoMoq)](https://www.nuget.org/packages/LoFuUnit.AutoMoq)\n[![LoFuUnit.AutoNSubstitute](https://img.shields.io/nuget/v/LoFuUnit.AutoNSubstitute.svg?label=LoFuUnit.AutoNSubstitute)](https://www.nuget.org/packages/LoFuUnit.AutoNSubstitute)\n\n\u003e Testing with **Lo**cal **Fu**nctions 🐯\n\u003e\n\u003e in .NET / C# ⚙️\n\u003e\n\u003e with your favorite **Unit** Testing Framework ✔️\n\n## Content\u003c!-- omit in toc --\u003e\n\n- [Introduction](#introduction)\n- [Testing](#testing)\n  - [Packages 📦](#packages-)\n  - [Tests ✔️](#tests-️)\n  - [Output 📃](#output-)\n  - [Limitations ❗](#limitations-)\n  - [Inconclusiveness ⁉️](#inconclusiveness-️)\n  - [Best Practices 👍](#best-practices-)\n- [Auto Mocking](#auto-mocking)\n  - [Packages 📦](#packages--1)\n  - [Mocks 🦆](#mocks-)\n  - [Limitations ❗](#limitations--1)\n- [Results](#results)\n- [Troubleshooting](#troubleshooting)\n- [Attribution](#attribution)\n\n## Introduction\n\nUse the traditional Unit Testing Frameworks for **BDD**.\n\nUse _local functions_ to structure tests with patterns like:\n\n- `Arrange` / `Act` / `Assert`\n- `Given` / `When` / `Then`\n- `Context` / `Specification`\n\nUse Auto-Mocking Containers to `Mock` / `Fake` / `Stub` dependencies.\n\nLoFuUnit consists of a few packages that make it convenient for developers to write tests with _collaboration_ \u0026 _communication_ in mind.\n\nWhy LoFu?\n`LoFu` stands for [Local Functions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions).\n\nWhy the tiger logo?\n[老虎](https://translate.google.com/#view=home\u0026op=translate\u0026sl=zh-CN\u0026tl=en\u0026text=%E8%80%81%E8%99%8E) translates to _tiger_ in English and is pronounced [`lou5 fu2`](https://forvo.com/word/%E8%80%81%E8%99%8E/#yue) in Cantonese.\n\nWhat are local functions?\n\n\u003e Starting with C# 7.0, C# supports local functions.\n\u003e\n\u003e Local functions are private methods of a type that are nested in another member.\n\u003e\n\u003e Local functions can use the `async` modifier.\n\n## Testing\n\nAn example of a test with [LoFuUnit.NUnit](https://www.nuget.org/packages/LoFuUnit.NUnit/):\n\n```csharp\nusing FluentAssertions;\nusing LoFuUnit.NUnit;\nusing NUnit.Framework;\n\nnamespace LoFuUnit.Tests.Documentation\n{\n    public class AuthenticationTests\n    {\n        SecurityService Subject;\n        UserToken Token;\n\n        [LoFu, Test]\n        public void Authenticate_admin_users()\n        {\n            Subject = new SecurityService();\n\n            void when_authenticating_an_admin_user() =\u003e\n                Token = Subject.Authenticate(\"username\", \"password\");\n\n            void should_indicate_the_user_s_role() =\u003e\n                Token.Role.Should().Be(Roles.Admin);\n\n            void should_have_a_unique_session_id() =\u003e\n                Token.SessionId.Should().NotBeNull();\n        }\n    }\n}\n```\n\nOutput:\n\n```txt\nAuthenticate admin users\n  when authenticating an admin user\n  should indicate the user's role\n  should have a unique session id\n```\n\nTerminology:\n\n- Test fixture – a class that contains tests\n  - e.g. `AuthenticationTests`\n- Test method – a method in a test fixture that represents a test\n  - e.g. `Authenticate_admin_users`\n- Test function – a local function in a containing test method, that does something along the lines of _arrange_, _act_ or _assert_\n  - e.g. `when_authenticating_an_admin_user`, `should_indicate_the_user_s_role` and `should_have_a_unique_session_id`\n\n### Packages 📦\n\nREADME | Test Framework | NuGet | Sample\n--- | --- | --- | ---\n[LoFuUnit](LoFuUnit.md) | - | [![NuGet](https://buildstats.info/nuget/LoFuUnit)](https://www.nuget.org/packages/LoFuUnit/) | [`LoFuUnit.Sample`](/samples/LoFuUnit.Sample)\n[LoFuUnit.MSTest](LoFuUnit.MSTest.md) | MSTest | [![NuGet](https://buildstats.info/nuget/LoFuUnit.MSTest)](https://www.nuget.org/packages/LoFuUnit.MSTest/) | [`LoFuUnit.Sample.MSTest`](/samples/LoFuUnit.Sample.MSTest)\n[LoFuUnit.NUnit](LoFuUnit.NUnit.md) | NUnit | [![NuGet](https://buildstats.info/nuget/LoFuUnit.NUnit)](https://www.nuget.org/packages/LoFuUnit.NUnit/) | [`LoFuUnit.Sample.NUnit`](/samples/LoFuUnit.Sample.NUnit)\n[LoFuUnit.Xunit](LoFuUnit.Xunit.md) | Xunit | [![NuGet](https://buildstats.info/nuget/LoFuUnit.Xunit)](https://www.nuget.org/packages/LoFuUnit.Xunit/) | [`LoFuUnit.Sample.Xunit`](/samples/LoFuUnit.Sample.Xunit)\n\n### Tests ✔️\n\nTest fixtures can inherit the class `LoFuTest`.\n\nTest methods can contain local functions that are invoked implicitly.\nThese test functions can perform the _arrange_, _act_ or _assert_ steps of the test.\n\nThe `LoFuTest` base class provides two important methods for test fixtures.\nThe `Assert` and `AssertAsync` methods invokes the test functions in the containing test method.\nThe invocations will occur in the order that the test functions are declared.\nIf a test function fails, the test method fails directly. Any subsequent test functions in the test method will not be invoked.\nMake sure that all test methods actually invoke `Assert` or `AssertAsync`.\n\nTest fixtures that does not inherit the `LoFuTest` base class can invoke the extension methods:\n\n- `this.Assert();`\n- `await this.AssertAsync();`\n\nThese methods has a `[CallerMemberName]` parameter.\nThe caller of these methods will implicitly be used, so don't set this parameter explicitly.\n\nTest fixtures can also be implemented so `Assert` or `AssertAsync` is invoked for all test methods in a _tear down / cleanup / dispose_ method.\n\nThe `LoFuUnit.NUnit` package also contains the `[LoFuTest]` and `[LoFu]` attributes to mark test methods with.\nThis will automatically invoke `Assert` or `AssertAsync` when the test method runs.\n\nExamples of all these patterns can be found in the [samples](/samples) folder.\n\nSuccinct test functions can be implemented as one-liners by\nrefactoring to _[expression body](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#methods)_\nand omitting the [curly brackets](https://en.wikipedia.org/wiki/Bracket#Curly_brackets).\n\nThe naming of the test methods and test functions are important, because the names are the basis for the test output.\n\nRemember:\n\n- Do **not** explicitly invoke the test functions in a test method\n- Only invoke the `Assert` or `AssertAsync` method **once** per test method\n\n### Output 📃\n\n![Visual Studio](vs.png)\n\nThe naming of the test methods and test functions determines the test output.\n\nThe naming convention is to use `snake_case` for test methods and test functions.\n\nAn underscore will be replaced by _space_ in the test output:\n\n- `foo_bar` will be formatted as `foo bar`\n\nSurrounding a word with double underscores will put _quotes_ around that word in the test output:\n\n- `__Foo__` will be formatted as `\"Foo\"`\n\nSuffixing a word with `_s_` will add _possessive_ form to the word in the test output:\n\n- `Foo_s_` will be formatted as `Foo's`\n\nTake the opportunity to invent your own convention for naming test methods and test functions.\n\nConsider using keywords like:\n\n- `given`\n- `when`\n- `then`\n- `should`\n\nWhat characters can be used when naming test methods and test functions?\n[Stack Overflow](https://stackoverflow.com/a/950651/7042367) has the answer!\n\nYou can use the `Log` method from the `LoFuTest` base class to write custom messages to the test output.\n\nThe naming of `assemblies`, `namespaces` and `classes` can also make the test suite more readable in your test runner.\n\n### Limitations ❗\n\n**Rule #1**: Test functions must return `void` or `Task`\n\nThe `Assert` method can only invoke local functions that:\n\n- are *synchronous* and returns `void`\n\nThe `AssertAsync` method can only invoke local functions that:\n\n- are *synchronous* and returns `void`, or\n- are *asynchronous* and returns `Task`\n\n**Rule #2**: Test functions must be parameterless\n\nThe `Assert` and `AssertAsync` methods can only invoke local functions that:\n\n- has no parameters\n\n**Rule #3**: Test functions must **not** use variables declared at test method scope, i.e. local variables in the containing method (including its parameters)\n\nThe solution for this is to use test fixture [members](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/members), e.g. fields, properties, methods etc.\n\nWhen a test function needs access to data from the test method or another test function:\n\n- use a variable declared at test fixture scope, i.e. a [field](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/fields)\n\nIt is easy to break rule #3 by mistake, because:\n\n\u003e Note that all local variables that are defined in the containing member, including its method parameters, are accessible in the local function.\n\n### Inconclusiveness ⁉️\n\nTest functions that break these abovementioned rules can not be invoked when the test method is running.\n\nThe `Assert` and `AssertAsync` methods will first validate the local functions in the test method scope, before they are invoked.\nIf any violations of the rules are found, an `InconclusiveLoFuTestException` is thrown.\nThe exception includes a message with the local functions that should be reviewed.\n\nWith the `[LoFu]` and `[LoFuTest]` attributes in [LoFuUnit.NUnit](https://www.nuget.org/packages/LoFuUnit.NUnit/), the test runner clearly shows inconclusive tests:\n\n![Inconclusive](inconclusive.png)\n\n### Best Practices 👍\n\nA list of [Best Practices](BestPractices.md) with patterns to consider and avoid.\n\n## Auto Mocking\n\nAn example of a test with [LoFuUnit.AutoNSubstitute](https://www.nuget.org/packages/LoFuUnit.AutoNSubstitute/) and [LoFuUnit.NUnit](https://www.nuget.org/packages/LoFuUnit.NUnit/):\n\n```csharp\nusing System;\nusing FluentAssertions;\nusing LoFuUnit.AutoNSubstitute;\nusing LoFuUnit.NUnit;\nusing NSubstitute;\nusing NUnit.Framework;\n\nnamespace LoFuUnit.Tests.Documentation\n{\n    public class MoodTests : LoFuTest\u003cMoodIdentifier\u003e\n    {\n        string _mood;\n\n        [LoFu, Test]\n        public void Identify_mood_on_mondays()\n        {\n            void given_the_current_day_is_monday()\n            {\n                var monday = new DateTime(2011, 2, 14);\n\n                Use\u003cISystemClock\u003e()\n                    .CurrentTime\n                    .Returns(monday);\n            }\n\n            void when_identifying_my_mood() =\u003e\n                _mood = Subject.IdentifyMood();\n\n            void should_be_pretty_bad() =\u003e\n                _mood.Should().Be(\"Pretty bad\");\n        }\n    }\n}\n```\n\nOutput:\n\n```txt\nIdentify mood on mondays\n  given the current day is monday\n  when identifying my mood\n  should be pretty bad\n```\n\n### Packages 📦\n\nREADME | Mock Framework | NuGet | Sample\n--- | --- | --- | ---\n[LoFuUnit.AutoFakeItEasy](LoFuUnit.AutoFakeItEasy.md) | FakeItEasy | [![NuGet](https://buildstats.info/nuget/LoFuUnit.AutoFakeItEasy)](https://www.nuget.org/packages/LoFuUnit.AutoFakeItEasy/) | [`LoFuUnit.Sample.AutoFakeItEasy`](/samples/LoFuUnit.Sample.AutoFakeItEasy)\n[LoFuUnit.AutoMoq](LoFuUnit.AutoMoq.md) | Moq | [![NuGet](https://buildstats.info/nuget/LoFuUnit.AutoMoq)](https://www.nuget.org/packages/LoFuUnit.AutoMoq/) | [`LoFuUnit.Sample.AutoMoq`](/samples/LoFuUnit.Sample.AutoMoq)\n[LoFuUnit.AutoNSubstitute](LoFuUnit.AutoNSubstitute.md) | NSubstitute | [![NuGet](https://buildstats.info/nuget/LoFuUnit.AutoNSubstitute)](https://www.nuget.org/packages/LoFuUnit.AutoNSubstitute/) | [`LoFuUnit.Sample.AutoNSubstitute`](/samples/LoFuUnit.Sample.AutoNSubstitute)\n\n### Mocks 🦆\n\n`LoFuUnit` uses `AutoFixture` as Auto-Mocking Container.\n\nTest fixtures that inherit the `LoFuTest\u003cTSubject\u003e` base class can use mocks.\nThe generic type parameter defines what kind of _subject_ under test to create.\n\nThe `Use\u003cTDependency\u003e` method creates a mock / dependency that the _subject_ is dependent upon.\nUse the API from the mock framework to configure the behavior of the mock.\n\nThe `The\u003cTDependency\u003e` method returns a previously created mock.\nUse the API from the mock framework to verify the interaction with the mock.\n\nThe `Subject` property returns an auto-mocked instance of the _subject_ under test.\nUse the _subject_ for the `act` or `when` test steps.\n\nThe `Clear` method reset the Auto-Mocking Container, and clears the mocks and _subject_ under test.\nMake test methods isolated from each other, by clearing the state between runs.\nTest fixtures can also be implemented so `Clear`is invoked for all test methods in a _tear down / cleanup / dispose_ method.\n\nThe `Fixture` property is exposed to the test fixtures. Use it in scenarios where the methods described above are inadequate.\nConsult the [AutoFixture documentation](https://github.com/AutoFixture/AutoFixture/wiki) for more information.\n\nExamples of usage can be found in the [samples](/samples) folder.\n\n### Limitations ❗\n\nBefore you can access a mock / dependency via the `The\u003cTDependency\u003e` method,\nyou must first call one of the `Use\u003cTDependency\u003e` methods for that specific type.\nThe `The\u003cTDependency\u003e` method will return `null` for unknown mocks / dependencies.\n\n## Results\n\nThe test result output can be used as documentation.\n\nWith the [`dotnet test`](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test) command, the output can be captured via:\n\n```bat\ndotnet test --logger:\"console;verbosity=detailed\"\n```\n\n![dotnet test --logger:\"console;verbosity=detailed\"](dotnet-test-logger-console.png)\n\n```bat\ndotnet test --logger:trx\n```\n\n![dotnet test --logger:trx](dotnet-test-logger-trx.png)\n\n## Troubleshooting\n\nIf you see something like this:\n\n```txt\nLoFuUnit.InconclusiveLoFuTestException : Invocation of test method 'when_Assert_on_inconclusive_test_method' aborted. One or more test functions are inconclusive. Test functions must be parameterless, and cannot use variables declared at test method scope. Please review the following local functions:\n  should_not_invoke_test_function_that_access_test_method_variables\n   at LoFuUnit.LoFuTest.ThrowInconclusive(MethodBase method, IEnumerable`1 names) in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 146\n   at LoFuUnit.LoFuTest.Validate(MethodBase method) in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 118\n   at LoFuUnit.LoFuTest.Assert(Object testFixture, MethodBase testMethod) in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 41\n   at LoFuUnit.LoFuTest.Assert() in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 24\n   at LoFuUnit.Tests.Integration.LoFuTestTests.when_Assert_on_inconclusive_test_method() in C:\\work\\github\\LoFuUnit\\tests\\LoFuUnit.Tests\\Integration\\LoFuTestTests.cs:line 89\n```\n\nThen you broke Rule #2 or #3, described above.\nMake sure that the local functions do not have any parameters.\nAnd furthermore, that the local functions do not access any variables declared in the containing method.\nRewrite the test so that data is passed to the local functions via fields declared in the containing class.\n\nIf you see something like this:\n\n```txt\nLoFuUnit.InconclusiveLoFuTestException : Invocation of test function 'should_not_invoke_async_test_function_that_returns_void' failed. The asynchronous local function does not have a valid return type. Asynchronous test functions must return a Task, and cannot return void or Task\u003cTResult\u003e.\n   at LoFuUnit.LoFuTest.AssertAsync(Object testFixture, MethodBase testMethod) in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 84\n   at LoFuUnit.LoFuTest.AssertAsync() in C:\\work\\github\\LoFuUnit\\src\\LoFuUnit\\LoFuTest.cs:line 36\n   at LoFuUnit.Tests.Integration.LoFuTestTests.when_AssertAsync_on_invalid_test_function() in C:\\work\\github\\LoFuUnit\\tests\\LoFuUnit.Tests\\Integration\\LoFuTestTests.cs:line 129\n   at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.GetResult()\n   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(Func`1 invoke)\n   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)\n   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)\n   at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.\u003c\u003ec__DisplayClass1_0.\u003cExecute\u003eb__0()\n   at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)\n```\n\nThen you broke Rule #1, described above.\nMake sure that the `async` local functions does not return `void`.\nRewrite the test so that the asynchronous local functions return a `Task`.\n\n## Attribution\n\nLoFuUnit is standing on the shoulders of giants.\n\nIt is inspired by https://github.com/machine/machine.specifications and https://github.com/machine/machine.specifications.fakes\n\nIt builds upon:\n\n- https://github.com/nunit/nunit\n- https://github.com/xunit/xunit\n- https://github.com/Microsoft/testfx\n- https://github.com/AutoFixture/AutoFixture\n- https://github.com/FakeItEasy/FakeItEasy\n- https://github.com/moq/moq4\n- https://github.com/nsubstitute/NSubstitute\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhlaueriksson%2Flofuunit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhlaueriksson%2Flofuunit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhlaueriksson%2Flofuunit/lists"}