{"id":17182347,"url":"https://github.com/simoncropp/xunitcontext","last_synced_at":"2026-04-04T14:26:09.635Z","repository":{"id":34597295,"uuid":"180539584","full_name":"SimonCropp/XunitContext","owner":"SimonCropp","description":"Extends xUnit to expose extra context and simplify logging","archived":false,"fork":false,"pushed_at":"2025-05-06T12:19:13.000Z","size":1163,"stargazers_count":167,"open_issues_count":1,"forks_count":16,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-08T13:26:57.538Z","etag":null,"topics":["itestoutputhelper","xunit"],"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/SimonCropp.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":"code_of_conduct.md","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,"zenodo":null},"funding":{"github":"SimonCropp"}},"created_at":"2019-04-10T08:41:04.000Z","updated_at":"2025-05-06T12:19:17.000Z","dependencies_parsed_at":"2024-01-10T00:49:56.306Z","dependency_job_id":"f9b40869-7fb9-4900-9c19-0a59ec04ac19","html_url":"https://github.com/SimonCropp/XunitContext","commit_stats":{"total_commits":1048,"total_committers":11,"mean_commits":95.27272727272727,"dds":0.4637404580152672,"last_synced_commit":"90b0b5cf218d5af3a3abc47964ce022fc21a0649"},"previous_names":["simoncropp/xunitlogger"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FXunitContext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FXunitContext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FXunitContext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonCropp%2FXunitContext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SimonCropp","download_url":"https://codeload.github.com/SimonCropp/XunitContext/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254292039,"owners_count":22046426,"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":["itestoutputhelper","xunit"],"created_at":"2024-10-15T00:36:51.112Z","updated_at":"2026-04-04T14:26:09.621Z","avatar_url":"https://github.com/SimonCropp.png","language":"C#","funding_links":["https://github.com/sponsors/SimonCropp"],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"/src/icon.png\" height=\"30px\"\u003e XunitContext\n\n[![Build status](https://img.shields.io/appveyor/build/SimonCropp/XunitContext)](https://ci.appveyor.com/project/SimonCropp/XunitContext)\n[![NuGet Status](https://img.shields.io/nuget/v/XunitContext.svg)](https://www.nuget.org/packages/XunitContext/)\n\nExtends [xUnit](https://xunit.net/) to expose extra context and simplify logging.\n\nRedirects [Trace.Write](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.write), [Debug.Write](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.write), and [Console.Write and Console.Error.Write](https://docs.microsoft.com/en-us/dotnet/api/system.console.write) to [ITestOutputHelper](https://xunit.net/docs/capturing-output). Also provides static access to the current [ITestOutputHelper](https://xunit.net/docs/capturing-output) for use within testing utility methods.\n\nUses [AsyncLocal](https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1) to track state.\n\n**See [Milestones](../../milestones?state=closed) for release notes.**\n\n\n## NuGet package\n\nhttps://nuget.org/packages/XunitContext/\n\n\n## ClassBeingTested\n\n\u003c!-- snippet: ClassBeingTested.cs --\u003e\n\u003ca id='snippet-ClassBeingTested.cs'\u003e\u003c/a\u003e\n```cs\nstatic class ClassBeingTested\n{\n    public static void Method()\n    {\n        Trace.WriteLine(\"From Trace\");\n        Console.WriteLine(\"From Console\");\n        Debug.WriteLine(\"From Debug\");\n        Console.Error.WriteLine(\"From Console Error\");\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ClassBeingTested.cs#L1-L10' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ClassBeingTested.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n## XunitContextBase\n\n`XunitContextBase` is an abstract base class for tests. It exposes logging methods for use from unit tests, and handle the flushing of logs in its `Dispose` method. `XunitContextBase` is actually a thin wrapper over `XunitContext`. `XunitContext`s `Write*` methods can also be use inside a test inheriting from `XunitContextBase`.\n\n\u003c!-- snippet: TestBaseSample.cs --\u003e\n\u003ca id='snippet-TestBaseSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class TestBaseSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void Write_lines()\n    {\n        WriteLine(\"From Test\");\n        ClassBeingTested.Method();\n\n        var logs = XunitContext.Logs;\n\n        Assert.Contains(\"From Test\", logs);\n        Assert.Contains(\"From Trace\", logs);\n        Assert.Contains(\"From Debug\", logs);\n        Assert.Contains(\"From Console\", logs);\n        Assert.Contains(\"From Console Error\", logs);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/TestBaseSample.cs#L1-L18' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-TestBaseSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n## xunit Fixture\n\nIn addition to `XunitContextBase` class approach, one is also possible to use  `IContextFixture` to gain access to `XunitContext` :\n\n\u003c!-- snippet: FixtureSample.cs --\u003e\n\u003ca id='snippet-FixtureSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class FixtureSample(ITestOutputHelper helper, ContextFixture ctxFixture) :\n    IContextFixture\n{\n    Context context = ctxFixture.Start(helper);\n\n    [Fact]\n    public void Usage()\n    {\n        Console.WriteLine(\"From Test\");\n        Assert.Contains(\"From Test\", context.LogMessages);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/FixtureSample.cs#L1-L12' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-FixtureSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n## Logging\n\n`XunitContext` provides static access to the logging state for tests. It exposes logging methods for use from unit tests, however registration of [ITestOutputHelper](https://xunit.net/docs/capturing-output) and flushing of logs must be handled explicitly.\n\n\u003c!-- snippet: XunitLoggerSample.cs --\u003e\n\u003ca id='snippet-XunitLoggerSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class XunitLoggerSample :\n    IDisposable\n{\n    [Fact]\n    public void Usage()\n    {\n        XunitContext.WriteLine(\"From Test\");\n\n        ClassBeingTested.Method();\n\n        var logs = XunitContext.Logs;\n\n        Assert.Contains(\"From Test\", logs);\n        Assert.Contains(\"From Trace\", logs);\n        Assert.Contains(\"From Debug\", logs);\n        Assert.Contains(\"From Console\", logs);\n        Assert.Contains(\"From Console Error\", logs);\n    }\n\n    public XunitLoggerSample(ITestOutputHelper testOutput) =\u003e\n        XunitContext.Register(testOutput);\n\n    public void Dispose() =\u003e\n        XunitContext.Flush();\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/XunitLoggerSample.cs#L1-L25' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-XunitLoggerSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n`XunitContext` redirects [Trace.Write](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.write), [Console.Write](https://docs.microsoft.com/en-us/dotnet/api/system.console.write), and [Debug.Write](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.write) in its static constructor.\n\n\u003c!-- snippet: writeRedirects --\u003e\n\u003ca id='snippet-writeRedirects'\u003e\u003c/a\u003e\n```cs\nTrace.Listeners.Clear();\nTrace.Listeners.Add(new TraceListener());\n#if (NETFRAMEWORK)\nDebug.Listeners.Clear();\nDebug.Listeners.Add(new TraceListener());\n#else\nDebugPoker.Overwrite(\n    text =\u003e\n    {\n        if (string.IsNullOrEmpty(text))\n        {\n            return;\n        }\n\n        if (text.EndsWith(Environment.NewLine))\n        {\n            WriteLine(text.TrimTrailingNewline());\n            return;\n        }\n\n        Write(text);\n    });\n#endif\nTestWriter writer = new();\nConsole.SetOut(writer);\nConsole.SetError(writer);\n```\n\u003csup\u003e\u003ca href='/src/XunitContext/XunitContext.cs#L43-L72' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-writeRedirects' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nThese API calls are then routed to the correct xUnit [ITestOutputHelper](https://xunit.net/docs/capturing-output) via a static [AsyncLocal](https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1).\n\n\n### Logging Libs\n\nApproaches to routing common logging libraries to Diagnostics.Trace:\n\n * [Serilog](https://serilog.net/) use [Serilog.Sinks.Trace](https://github.com/serilog/serilog-sinks-trace).\n * [NLog](https://github.com/NLog/NLog) use a [Trace target](https://github.com/NLog/NLog/wiki/Trace-target).\n\n\n## Filters\n\n`XunitContext.Filters` can be used to filter out unwanted lines:\n\n\u003c!-- snippet: FilterSample.cs --\u003e\n\u003ca id='snippet-FilterSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class FilterSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    static FilterSample() =\u003e\n        Filters.Add(_ =\u003e _ != null \u0026\u0026 !_.Contains(\"ignored\"));\n\n    [Fact]\n    public void Write_lines()\n    {\n        WriteLine(\"first\");\n        WriteLine(\"with ignored string\");\n        WriteLine(\"last\");\n        var logs = XunitContext.Logs;\n\n        Assert.Contains(\"first\", logs);\n        Assert.DoesNotContain(\"with ignored string\", logs);\n        Assert.Contains(\"last\", logs);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/FilterSample.cs#L1-L19' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-FilterSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nFilters are static and shared for all tests.\n\n\n## Context\n\nFor every tests there is a contextual API to perform several operations.\n\n * `Context.TestOutput`: Access to [ITestOutputHelper](https://xunit.net/docs/capturing-output).\n * `Context.Write` and `Context.WriteLine`: Write to the current log.\n * `Context.LogMessages`: Access to all log message for the current test.\n * [Counters](#counters): Provide access in predicable and incrementing values for the following types: `Guid`, `Int`, `Long`, `UInt`, and `ULong`.\n * `Context.Test`: Access to the current `ITest`.\n * `Context.SourceFile`: Access to the file path for the current test.\n * `Context.SourceDirectory`: Access to the directory path for the current test.\n * `Context.SolutionDirectory`: The current solution directory. Obtained by walking up the directory tree from `SourceDirectory`.\n * `Context.TestException`: Access to the exception if the current test has failed. See [Test Failure](test-failure).\n\n\u003c!-- snippet: ContextSample.cs --\u003e\n\u003ca id='snippet-ContextSample.cs'\u003e\u003c/a\u003e\n```cs\n// ReSharper disable UnusedVariable\n\npublic class ContextSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void Usage()\n    {\n        Context.WriteLine(\"Some message\");\n\n        var currentLogMessages = Context.LogMessages;\n\n        var testOutputHelper = Context.TestOutput;\n\n        var currentTest = Context.Test;\n\n        var sourceFile = Context.SourceFile;\n\n        var sourceDirectory = Context.SourceDirectory;\n\n        var solutionDirectory = Context.SolutionDirectory;\n\n        var currentTestException = Context.TestException;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ContextSample.cs#L1-L25' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ContextSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nSome members are pushed down to the be accessible directly from `XunitContextBase`:\n\n\u003c!-- snippet: ContextPushedDownSample.cs --\u003e\n\u003ca id='snippet-ContextPushedDownSample.cs'\u003e\u003c/a\u003e\n```cs\n// ReSharper disable UnusedVariable\n\npublic class ContextPushedDownSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void Usage()\n    {\n        WriteLine(\"Some message\");\n\n        var currentLogMessages = Logs;\n\n        var testOutputHelper = Output;\n\n        var sourceFile = SourceFile;\n\n        var sourceDirectory = SourceDirectory;\n\n        var solutionDirectory = SolutionDirectory;\n\n        var currentTestException = TestException;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ContextPushedDownSample.cs#L1-L23' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ContextPushedDownSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nContext can accessed via a static API:\n\n\u003c!-- snippet: ContextStaticSample.cs --\u003e\n\u003ca id='snippet-ContextStaticSample.cs'\u003e\u003c/a\u003e\n```cs\n// ReSharper disable UnusedVariable\n\npublic class ContextStaticSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void StaticUsage()\n    {\n        XunitContext.Context.WriteLine(\"Some message\");\n\n        var currentLogMessages = XunitContext.Context.LogMessages;\n\n        var testOutputHelper = XunitContext.Context.TestOutput;\n\n        var currentTest = XunitContext.Context.Test;\n\n        var sourceFile = XunitContext.Context.SourceFile;\n\n        var sourceDirectory = XunitContext.Context.SourceDirectory;\n\n        var solutionDirectory = XunitContext.Context.SolutionDirectory;\n\n        var currentTestException = XunitContext.Context.TestException;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ContextStaticSample.cs#L1-L25' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ContextStaticSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Current Test\n\nThere is currently no API in xUnit to retrieve information on the current test. See issues [#1359](https://github.com/xunit/xunit/issues/1359), [#416](https://github.com/xunit/xunit/issues/416), and [#398](https://github.com/xunit/xunit/issues/398).\n\nTo work around this, this project exposes the current instance of `ITest` via reflection.\n\nUsage:\n\n\u003c!-- snippet: CurrentTestSample.cs --\u003e\n\u003ca id='snippet-CurrentTestSample.cs'\u003e\u003c/a\u003e\n```cs\n// ReSharper disable UnusedVariable\n\npublic class CurrentTestSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void Usage()\n    {\n        var currentTest = Context.Test;\n        // DisplayName will be 'CurrentTestSample.Usage'\n        var displayName = currentTest.DisplayName;\n    }\n\n    [Fact]\n    public void StaticUsage()\n    {\n        var currentTest = XunitContext.Context.Test;\n        // DisplayName will be 'CurrentTestSample.StaticUsage'\n        var displayName = currentTest.DisplayName;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/CurrentTestSample.cs#L1-L21' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-CurrentTestSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nImplementation:\n\n\u003c!-- snippet: Context_CurrentTest.cs --\u003e\n\u003ca id='snippet-Context_CurrentTest.cs'\u003e\u003c/a\u003e\n```cs\nnamespace Xunit;\n\npublic partial class Context\n{\n    ITest? test;\n\n\n    public ITest Test\n    {\n        get\n        {\n            InitTest();\n\n            return test!;\n        }\n    }\n\n    MethodInfo? methodInfo;\n\n    public MethodInfo MethodInfo\n    {\n        get\n        {\n            InitTest();\n            return methodInfo!;\n        }\n    }\n\n    Type? testType;\n\n    public Type TestType\n    {\n        get\n        {\n            InitTest();\n            return testType!;\n        }\n    }\n\n    void InitTest()\n    {\n        if (test != null)\n        {\n            return;\n        }\n\n        if (TestOutput == null)\n        {\n            throw new(MissingTestOutput);\n        }\n\n#if NET8_0_OR_GREATER\n        [UnsafeAccessor(UnsafeAccessorKind.Field, Name = \"test\")]\n        static extern ref ITest GetTest(TestOutputHelper? c);\n        test = GetTest((TestOutputHelper) TestOutput);\n#else\n        test = (ITest) GetTestMethod(TestOutput)\n            .GetValue(TestOutput)!;\n#endif\n        var method = (ReflectionMethodInfo) test.TestCase.TestMethod.Method;\n        var type = (ReflectionTypeInfo) test.TestCase.TestMethod.TestClass.Class;\n        methodInfo = method.MethodInfo;\n        testType = type.Type;\n    }\n\n    public const string MissingTestOutput = \"ITestOutputHelper has not been set. It is possible that the call to `XunitContext.Register()` is missing, or the current test does not inherit from `XunitContextBase`.\";\n\n#if !NET8_0_OR_GREATER\n    static FieldInfo? cachedTestMember;\n\n    static FieldInfo GetTestMethod(ITestOutputHelper testOutput)\n    {\n        if (cachedTestMember != null)\n        {\n            return cachedTestMember;\n        }\n\n        var testOutputType = testOutput.GetType();\n        cachedTestMember = testOutputType.GetField(\"test\", BindingFlags.Instance | BindingFlags.NonPublic);\n        if (cachedTestMember == null)\n        {\n            throw new($\"Unable to find 'test' field on {testOutputType.FullName}\");\n        }\n\n        return cachedTestMember;\n    }\n#endif\n}\n```\n\u003csup\u003e\u003ca href='/src/XunitContext/Context_CurrentTest.cs#L1-L88' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-Context_CurrentTest.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Test Failure\n\nWhen a test fails it is expressed as an exception. The exception can be viewed by enabling exception capture, and then accessing `Context.TestException`. The `TestException` will be null if the test has passed.\n\nOne common case is to perform some logic, based on the existence of the exception, in the `Dispose` of a test.\n\n\u003c!-- snippet: TestExceptionSample --\u003e\n\u003ca id='snippet-TestExceptionSample'\u003e\u003c/a\u003e\n```cs\n// ReSharper disable UnusedVariable\npublic static class GlobalSetup\n{\n    [ModuleInitializer]\n    public static void Setup() =\u003e\n        XunitContext.EnableExceptionCapture();\n}\n\npublic class TestExceptionSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact(Skip = \"Will fail\")]\n    public void Usage() =\u003e\n        //This tests will fail\n        Assert.False(true);\n\n    public override void Dispose()\n    {\n        var theExceptionThrownByTest = Context.TestException;\n        var testDisplayName = Context.Test.DisplayName;\n        var testCase = Context.Test.TestCase;\n        base.Dispose();\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/TestExceptionSample.cs#L1-L28' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-TestExceptionSample' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Base Class\n\nWhen creating a custom base class for other tests, it is necessary to pass through the source file path to `XunitContextBase` via the constructor.\n\n\u003c!-- snippet: XunitContextCustomBase --\u003e\n\u003ca id='snippet-XunitContextCustomBase'\u003e\u003c/a\u003e\n```cs\npublic class CustomBase(\n    ITestOutputHelper testOutput,\n    [CallerFilePath] string sourceFile = \"\")\n    :\n        XunitContextBase(testOutput, sourceFile);\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/CustomBase.cs#L1-L9' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-XunitContextCustomBase' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Parameters\n\nProvided the parameters passed to the current test when using a `[Theory]`.\n\nUse cases:\n\n * To derive the [unique test name](#uniquetestname).\n * In extensibility scenarios for example [Verify file naming](https://github.com/SimonCropp/Verify/blob/master/docs/naming.md).\n\nUsage:\n\n\u003c!-- snippet: ParametersSample.cs --\u003e\n\u003ca id='snippet-ParametersSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class ParametersSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Theory]\n    [MemberData(nameof(GetData))]\n    public void Usage(string arg)\n    {\n        var parameter = Context.Parameters.Single();\n        var parameterInfo = parameter.Info;\n        Assert.Equal(\"arg\", parameterInfo.Name);\n        Assert.Equal(arg, parameter.Value);\n    }\n\n    public static IEnumerable\u003cobject[]\u003e GetData()\n    {\n        yield return [\"Value1\"];\n        yield return [\"Value2\"];\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ParametersSample.cs#L1-L19' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ParametersSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nImplementation:\n\n\u003c!-- snippet: Parameters --\u003e\n\u003ca id='snippet-Parameters'\u003e\u003c/a\u003e\n```cs\nstatic List\u003cParameter\u003e GetParameters(ITestCase testCase) =\u003e\n    GetParameters(testCase, testCase.TestMethodArguments);\n\nstatic List\u003cParameter\u003e GetParameters(ITestCase testCase, object[] arguments)\n{\n    var method = testCase.TestMethod;\n    var infos = method\n        .Method.GetParameters()\n        .ToList();\n    if (arguments == null || arguments.Length == 0)\n    {\n        if (infos.Count == 0)\n        {\n            return empty;\n        }\n\n        throw NewNoArgumentsDetectedException();\n    }\n\n    List\u003cParameter\u003e items = [];\n\n    for (var index = 0; index \u003c infos.Count; index++)\n    {\n        items.Add(new(infos[index], arguments[index]));\n    }\n\n    return items;\n}\n```\n\u003csup\u003e\u003ca href='/src/XunitContext/Context_Parameters.cs#L20-L51' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-Parameters' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Complex parameters\n\nOnly core types (string, int, DateTime etc) can use the above automated approach. If a complex type is used the following exception will be thrown\n\n\u003c!-- include: NoArgumentsDetectedException. path: /src/Tests/NoArgumentsDetectedException.include.md --\u003e\n\u003e No arguments detected for method with parameters.\n\u003e This is most likely caused by using a parameter that Xunit cannot serialize.\n\u003e Instead pass in a simple type as a parameter and construct the complex object inside the test.\n\u003e Alternatively; override the current parameters using `UseParameters()` via the current test base class, or via `XunitContext.Current.UseParameters()`.\n\u003c!-- endInclude --\u003e\n\nTo use complex types override the parameter resolution using `XunitContextBase.UseParameters`:\n\n\u003c!-- snippet: ComplexParameterSample.cs --\u003e\n\u003ca id='snippet-ComplexParameterSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class ComplexParameterSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Theory]\n    [MemberData(nameof(GetData))]\n    public void UseComplexMemberData(ComplexClass arg)\n    {\n        UseParameters(arg);\n        var parameter = Context.Parameters.Single();\n        var parameterInfo = parameter.Info;\n        Assert.Equal(\"arg\", parameterInfo.Name);\n        Assert.Equal(arg, parameter.Value);\n    }\n\n    public static IEnumerable\u003cobject[]\u003e GetData()\n    {\n        yield return [new ComplexClass(\"Value1\")];\n        yield return [new ComplexClass(\"Value2\")];\n    }\n\n    public class ComplexClass(string value)\n    {\n        public string Value { get; } = value;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/ComplexParameterSample.cs#L1-L25' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-ComplexParameterSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### UniqueTestName\n\nProvided a string that uniquely identifies a test case.\n\nUsage:\n\n\u003c!-- snippet: UniqueTestNameSample.cs --\u003e\n\u003ca id='snippet-UniqueTestNameSample.cs'\u003e\u003c/a\u003e\n```cs\npublic class UniqueTestNameSample(ITestOutputHelper output) :\n    XunitContextBase(output)\n{\n    [Fact]\n    public void Usage()\n    {\n        var testName = Context.UniqueTestName;\n\n        Context.WriteLine(testName);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Snippets/UniqueTestNameSample.cs#L1-L11' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-UniqueTestNameSample.cs' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nImplementation:\n\n\u003c!-- snippet: UniqueTestName --\u003e\n\u003ca id='snippet-UniqueTestName'\u003e\u003c/a\u003e\n```cs\nstring GetUniqueTestName(ITestCase testCase)\n{\n    var method = testCase.TestMethod;\n    var name = $\"{method.TestClass.Class.ClassName()}.{method.Method.Name}\";\n    if (!Parameters.Any())\n    {\n        return name;\n    }\n\n    var builder = new StringBuilder($\"{name}_\");\n    foreach (var parameter in Parameters)\n    {\n        builder.Append($\"{parameter.Info.Name}=\");\n        builder.Append(string.Join(\",\", SplitParams(parameter.Value)));\n        builder.Append('_');\n    }\n\n    builder.Length -= 1;\n\n    return builder.ToString();\n}\n\nstatic IEnumerable\u003cstring\u003e SplitParams(object? parameter)\n{\n    if (parameter == null)\n    {\n        yield return \"null\";\n        yield break;\n    }\n\n    if (parameter is string stringValue)\n    {\n        yield return stringValue;\n        yield break;\n    }\n\n    if (parameter is IEnumerable enumerable)\n    {\n        foreach (var item in enumerable)\n        {\n            foreach (var sub in SplitParams(item))\n            {\n                yield return sub;\n            }\n        }\n\n        yield break;\n    }\n\n    var toString = parameter.ToString();\n    if (toString == null)\n    {\n        yield return \"null\";\n    }\n    else\n    {\n        yield return toString;\n    }\n}\n```\n\u003csup\u003e\u003ca href='/src/XunitContext/Context_TestName.cs#L26-L88' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-UniqueTestName' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n## Global Setup\n\nXunit has no way to run code once before any tests executing. So use one of the following:\n\n * [C# 9 Module Initializer](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers).\n * [Fody Module Initializer](https://github.com/Fody/ModuleInit).\n * Having a single base class that all tests inherit from, and place any configuration code in the static constructor of that type.\n\n\n## Icon\n\n[Wolverine](https://thenounproject.com/term/wolverine/18415/) designed by [Mike Rowe](https://thenounproject.com/itsmikerowe/) from [The Noun Project](https://thenounproject.com/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimoncropp%2Fxunitcontext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimoncropp%2Fxunitcontext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimoncropp%2Fxunitcontext/lists"}