{"id":19080535,"url":"https://github.com/zvirja/nsubstitute.community.diagnostics","last_synced_at":"2026-06-26T09:31:20.096Z","repository":{"id":43764778,"uuid":"171174381","full_name":"zvirja/NSubstitute.Community.Diagnostics","owner":"zvirja","description":"Diagnostics library to troubleshoot NSubstitute issues","archived":false,"fork":false,"pushed_at":"2022-07-07T15:54:10.000Z","size":52,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-03T21:45:25.201Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/zvirja.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-17T21:26:37.000Z","updated_at":"2022-07-07T15:54:13.000Z","dependencies_parsed_at":"2022-08-22T14:50:29.188Z","dependency_job_id":null,"html_url":"https://github.com/zvirja/NSubstitute.Community.Diagnostics","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zvirja%2FNSubstitute.Community.Diagnostics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zvirja%2FNSubstitute.Community.Diagnostics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zvirja%2FNSubstitute.Community.Diagnostics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zvirja%2FNSubstitute.Community.Diagnostics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zvirja","download_url":"https://codeload.github.com/zvirja/NSubstitute.Community.Diagnostics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240131737,"owners_count":19752727,"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":[],"created_at":"2024-11-09T02:24:17.606Z","updated_at":"2026-05-22T04:30:22.628Z","avatar_url":"https://github.com/zvirja.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build status](https://ci.appveyor.com/api/projects/status/7xumirowuxo3hfrv/branch/master?svg=true)](https://ci.appveyor.com/project/Zvirja/nsubstitute-community-diagnostics/branch/master) [![NuGet version](https://img.shields.io/nuget/vpre/NSubstitute.Community.Diagnostics.svg)](https://www.nuget.org/packages/NSubstitute.Community.Diagnostics)\n\n# NSubstitute.Community.Diagnostics\n\nA tool to quickly identify the NSubstitute related issues. It overrides the most critical parts of NSubstitute to log most important actions. Later log might be used to quickly figure out the reason of misbehavior.\n\n# Installation and usage\n\nTo use any feature install the `NSubstitute.Community.Diagnostics` NuGet package to your test project first.\n\n## NSubstitute exceptions diagnostics\n\nThis diagnostics allows to retrieve more context about NSubstitute exception. When installed, it intercepts all NSubstitute related exceptions and appends NSubstitute log to them, which allows to troubleshoot issues easier.\n\nIdeally, you should install hook before any test run. NUnit and MSTest seem to support this feature natively. For xUnit you might want to use [Module Initializers feature](http://einaregilsson.com/module-initializers-in-csharp/) via [InjectModuleInitializer](https://github.com/kzu/InjectModuleInitializer) or [Fody ModuleInit](https://github.com/fody/moduleinit).\n\nRun the following code to install the hook:\n\n```c#\nNSubstituteExceptionDiagnostics.Install();\n\n```\n\nLater whever NSubstitute exception occur, look for the `**  NSubstitute LOG (reversed)  **` section in the exception message for more clues.\n\n[See output sample.](#nsubstitute-exception-message-with-enabled-exceptions-diagnostics)\n\n##  NSubstitute logging\n\nThis diagnostics allows you to install global hook to monitor all the important NSubstitute library activity. You might use either logging or tracing mode, depending on amount of details you require.\n\nTo log NSubstitute wrap your test code with the diagnostics context:\n\n```c#\nusing (NSubstituteDiagnosticsContext.InstallLogging(Console.WriteLine))\n{\n    var substitute = Substitute.For\u003cISut\u003e();\n    substitute.Echo(42).Returns(42);\n}\n```\n\nTo get more detailed output use tracing mode:\n\n```c#\nusing (NSubstituteDiagnosticsContext.InstallTracing(Console.WriteLine))\n{\n    var substitute = Substitute.For\u003cISut\u003e();\n    substitute.Echo(42).Returns(42);\n}\n```\n\nThen examine test output to get hints on the issue you have (or e.g. capture it and attach to the reported issue).\n\n### xUnit\n\nxUnit doesn't capture `Console.WriteLine` so use the `ITestOutputHelper` instead:\n\n```c#\npublic class xUnitSample\n{\n    private readonly ITestOutputHelper _output;\n    public xUnitSample(ITestOutputHelper output) =\u003e _output = output;\n\n    [Fact]\n    public void DiagnosticsSample()\n    {\n        using (NSubstituteDiagnosticsContext.InstallLogging(_output.WriteLine))\n        {\n            var substitute = Substitute.For\u003cISut\u003e();\n            substitute.Echo(42).Returns(42);\n        }\n    }\n}\n```\n\n# Concurrency\n\nDue to the nature of NSubstitute core the diagnostics context is global and affects all the threads while installed. In practice it means that you cannot install/uninstall the diagnostics concurrently in different tests, as it would lead to weird behavior.\n\nIf you need to troubleshoot a few tests or the whole test suite (e.g. issue happens only when all the tests are run concurrently), you can install the context once before any test run and log all the entries to the log file (don't forget to flush :blush:). In that case you might want to omit diagnostics context disposal, as no code is expected to run afterwards.\n\nNotice, the concurrency limitation is related to the installation only - the code you run while the context is installed might be fully concurrent.\n\n# Samples\n\n## NSubstitute exception message with enabled exceptions diagnostics\n\n```\nNSubstitute.Exceptions.RedundantArgumentMatcherException\nSome argument specifications (e.g. Arg.Is, Arg.Any) were left over after the last call.\n\n... trimmed\n\nDiagnostic information:\n\nRemaining (non-bound) argument specifications:\n    any Int32\n\nAll argument specifications:\n    any Int32\n    any Int32\n\n************************************************************************\n*********************  NSubstitute LOG (reversed)  *********************\n************************************************************************\n\n[Caller: ExceptionDiagnosticsTests.TestToTroubleshoot][Received call] Substitute: Substitute.ISut|00714c06 Call: Echo(Int32|0) Signature: Echo(Int32) -\u003e Int32 Argument specifications: [\u003cany Int32\u003e, \u003cany Int32\u003e]\n[Caller: ExceptionDiagnosticsTests.TestToTroubleshoot][Enqueue argument specification] Specification: \u003cany Int32\u003e \n[Caller: ExceptionDiagnosticsTests.TestToTroubleshoot][ProxyFactory] GenerateProxy(callRouter: \u003cCallRouter|01af8d3f\u003e, typeToProxy: Samples.ISut, additionalInterfaces: [], constructorArguments: []) =\u003e Substitute.ISut|00714c06\n[Caller: ExceptionDiagnosticsTests.CorruptedTest][Enqueue argument specification] Specification: \u003cany Int32\u003e \n[Caller: ExceptionDiagnosticsTests.RunTests][DiagnosticsContextInstaller] Installed diagnostics context\n\n************************************************************************\n************************************************************************\n```\n\n## Tracing sample\n\nCode:\n\n```c#\npublic class xUnitSample\n{\n    private readonly ITestOutputHelper _output;\n    public xUnitSample(ITestOutputHelper output) =\u003e _output = output;\n\n    [Fact]\n    public void DiagnosticsSample()\n    {\n        using (NSubstituteDiagnosticsContext.InstallLogging(_output.WriteLine))\n        {\n            var substitute = Substitute.For\u003cISut\u003e();\n            substitute.Echo(42).Returns(42);\n        }\n    }\n}\n```\n\nOutput:\n\n```\n[TID:15][Caller: xUnitSample.DiagnosticsSample][DiagnosticsContextInstaller] Installed diagnostics context\n[TID:15][Caller: xUnitSample.DiagnosticsSample][ProxyFactory] GenerateProxy(callRouter: \u003cCallRouter|03cc973e\u003e, typeToProxy: Samples.ISut, additionalInterfaces: [], constructorArguments: []) =\u003e Substitute.ISut|000ef682\n[TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext] DequeueAllArgumentSpecifications() =\u003e []\n[TID:15][Caller: xUnitSample.DiagnosticsSample][CallRouter] Route(call: \u003c[Substitute.ISut|000ef682].Echo(Int32|42) Signature: Echo(Int32) -\u003e Int32\u003e) [this: \u003cCallRouter|03cc973e#Substitute.ISut|000ef682\u003e]\n    [TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext] SetLastCallRouter(callRouter: \u003cCallRouter|03cc973e#Substitute.ISut|000ef682\u003e)\n    [TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext] UsePendingRaisingEventArgumentsFactory() =\u003e \u003cnull\u003e\n    [TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext] UseNextRoute(callRouter: \u003cCallRouter|03cc973e#Substitute.ISut|000ef682\u003e) =\u003e \u003cnull\u003e\n    [TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext.PendingSpecification] Clear()\n    [TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext.PendingSpecification] SetLastCall(call: \u003c[Substitute.ISut|000ef682].Echo(Int32|42) Signature: Echo(Int32) -\u003e Int32\u003e)\n[TID:15][Caller: xUnitSample.DiagnosticsSample][ThreadLocalContext] LastCallShouldReturn(value: Int32|42, matchArgs: \u003cas specified\u003e)\n[TID:15][Caller: xUnitSample.DiagnosticsSample][CallRouter] LastCallShouldReturn(value: Int32|42, matchArgs: \u003cas specified\u003e, pendingSpecInfo: \u003cCALL|\u003c[Substitute.ISut|000ef682].Echo(Int32|42) Signature: Echo(Int32) -\u003e Int32\u003e\u003e) [this: \u003cCallRouter|03cc973e#Substitute.ISut|000ef682\u003e]\n[TID:15][Caller: xUnitSample.DiagnosticsSample][DiagnosticsContextInstaller] Restored normal context\n```\n\nAs you can see, the output logs all the major events happened to the NSubstitute. Now depending on the issue, either brief or precise look might be required to figure out what's going wrong.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzvirja%2Fnsubstitute.community.diagnostics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzvirja%2Fnsubstitute.community.diagnostics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzvirja%2Fnsubstitute.community.diagnostics/lists"}