From c0320fec6597c7a22b7e45c7c58cde18af2e3926 Mon Sep 17 00:00:00 2001 From: Stefan Holpp Date: Thu, 29 Jan 2026 12:25:33 +0100 Subject: [PATCH 1/3] Enhanced docu for Execution Hooks to show users how to distinguish between SetUp and OneTimeSetUp inside a Hook. --- .../nunit/extending-nunit/Execution-Hooks.md | 10 ++++++++++ .../Snippets.NUnit/ExecutionHookExamples.cs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/articles/nunit/extending-nunit/Execution-Hooks.md b/docs/articles/nunit/extending-nunit/Execution-Hooks.md index 44f3cc8f9..25fc29560 100644 --- a/docs/articles/nunit/extending-nunit/Execution-Hooks.md +++ b/docs/articles/nunit/extending-nunit/Execution-Hooks.md @@ -47,6 +47,16 @@ Each hook receives a `HookData` instance: Use these fields for logging, conditional logic, or adaptive cleanup. +## One-Time vs Per-Test Setup/TearDown + +`BeforeEverySetUpHook`/`AfterEverySetUpHook` and `BeforeEveryTearDownHook`/`AfterEveryTearDownHook` run for both per-test and one-time setup/teardown. Inside a hook, the supported way to distinguish the two is the current test context: `Context.Test.IsSuite` is `true` for [OneTimeSetUp]/[OneTimeTearDown] (suite context) and `false` for [SetUp]/[TearDown] (test method context). + +See [Example: One-Time vs Per-Test Setup/TearDown](#example-one-time-vs-per-test-setup-teardown) for a complete hook implementation. + +## Example: One-Time vs Per-Test Setup/TearDown + +[!code-csharp[ExecutionHookAttributeExample](~/snippets/Snippets.NUnit/ExecutionHookExamples.cs#OneTimeVsPerTestSetupTearDownExample)] + ## Example: Measure Time for Setup [!code-csharp[ExecutionHookAttributeExample](~/snippets/Snippets.NUnit/ExecutionHookExamples.cs#TimingHookAttribute)] diff --git a/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs b/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs index 41b903d15..5abe6b6ad 100644 --- a/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs +++ b/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs @@ -30,6 +30,24 @@ public override void AfterEverySetUpHook(HookData hookData) } #endregion + #region OneTimeVsPerTestSetupTearDownExample + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] + public sealed class OneTimeVsPerTestSetupTearDownHookAttribute : ExecutionHookAttribute + { + public override void BeforeEverySetUpHook(HookData data) + { + var scope = data.Context.Test.IsSuite ? "OneTimeSetUp" : "SetUp"; + TestContext.Progress.WriteLine($"{scope}: {data.HookedMethod?.Name}"); + } + + public override void AfterEveryTearDownHook(HookData data) + { + var scope = data.Context.Test.IsSuite ? "OneTimeTearDown" : "TearDown"; + TestContext.Progress.WriteLine($"{scope}: {data.HookedMethod?.Name}"); + } + } + #endregion + #region Usage [TestFixture] [TimeMeasurementHook] From 7b6e60933a45d89f00216fcefbf090a82ad9b912 Mon Sep 17 00:00:00 2001 From: Stefan Holpp Date: Mon, 9 Feb 2026 08:16:29 +0100 Subject: [PATCH 2/3] Docs: fix Execution Hooks example heading (replace '/' with 'and') --- docs/articles/nunit/extending-nunit/Execution-Hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/nunit/extending-nunit/Execution-Hooks.md b/docs/articles/nunit/extending-nunit/Execution-Hooks.md index 25fc29560..092b0fc05 100644 --- a/docs/articles/nunit/extending-nunit/Execution-Hooks.md +++ b/docs/articles/nunit/extending-nunit/Execution-Hooks.md @@ -51,9 +51,9 @@ Use these fields for logging, conditional logic, or adaptive cleanup. `BeforeEverySetUpHook`/`AfterEverySetUpHook` and `BeforeEveryTearDownHook`/`AfterEveryTearDownHook` run for both per-test and one-time setup/teardown. Inside a hook, the supported way to distinguish the two is the current test context: `Context.Test.IsSuite` is `true` for [OneTimeSetUp]/[OneTimeTearDown] (suite context) and `false` for [SetUp]/[TearDown] (test method context). -See [Example: One-Time vs Per-Test Setup/TearDown](#example-one-time-vs-per-test-setup-teardown) for a complete hook implementation. +See [Example: One-Time vs Per-Test Setup and TearDown](#example-one-time-vs-per-test-setup-and-teardown) for a complete hook implementation. -## Example: One-Time vs Per-Test Setup/TearDown +## Example: One-Time vs Per-Test Setup and TearDown [!code-csharp[ExecutionHookAttributeExample](~/snippets/Snippets.NUnit/ExecutionHookExamples.cs#OneTimeVsPerTestSetupTearDownExample)] From ed334e76e3f0147141cd7f09b237ee19aae877ea Mon Sep 17 00:00:00 2001 From: Stefan Holpp Date: Mon, 9 Feb 2026 12:27:58 +0100 Subject: [PATCH 3/3] Writing to TestContext.Progress, not to Out. --- docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs b/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs index 5abe6b6ad..a5a38dbbd 100644 --- a/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs +++ b/docs/snippets/Snippets.NUnit/ExecutionHookExamples.cs @@ -22,7 +22,7 @@ public override void AfterEverySetUpHook(HookData hookData) if (_starts.TryGetValue(key, out var start)) { var elapsed = DateTime.UtcNow - start; - TestContext.WriteLine($"[Timing] " + + TestContext.Progress.WriteLine($"[Timing] " + $"{hookData.Context.Test.MethodName} " + $"took {elapsed.TotalMilliseconds:F1} ms"); } @@ -70,7 +70,7 @@ private void Log(string phase, HookData data, bool withException = false) var name = data.Context.Test.MethodName; var exInfo = withException && data.Exception != null ? $" (EX: {data.Exception.GetType().Name})" : string.Empty; - TestContext.WriteLine($"[{phase}] {name}{exInfo}"); + TestContext.Progress.WriteLine($"[{phase}] {name}{exInfo}"); } public override void BeforeEverySetUpHook(HookData d)