Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/articles/nunit/writing-tests/attributes/onetimesetup.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Methods may be **async** (`Task` / `Task<T>`): NUnit will wait for completion li
If `OneTimeSetUp` lives on an **abstract or concrete base test class**, NUnit invokes it **for every derived fixture
type** that inherits that hierarchy (along with setup on the descendants). When you truly need execution **exactly once
for many unrelated fixtures**, prefer **`SetUpFixture`** or **`static`** initialization instead of repeating base-class
`OneTimeSetUp` semantics.
`OneTimeSetUp` semantics. See [Sharing setup across projects](xref:attribute-setupfixture#sharing-setup-across-projects)
for the pattern to use when the base class lives in a shared package.

## Failures and exceptions

Expand Down
60 changes: 60 additions & 0 deletions docs/articles/nunit/writing-tests/attributes/setupfixture.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,73 @@ The defined order is as follows...
[Items 6 and 7 rarely come into play but the features are available for situations like code generation, where it may
be more convenient to have multiple setup fixtures and/or methods.]

## Inheritance

`[SetUpFixture]` can be inherited. A class that derives from a class marked `[SetUpFixture]` is itself a setup
fixture, even if it does not repeat the attribute. This lets a shared test-support package provide the one-time setup and
teardown in a base class, with each test project deriving from it:

[!code-csharp[SetUpFixtureInheritanceBase](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureInheritanceBase)]

[!code-csharp[SetUpFixtureInheritanceDerived](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureInheritanceDerived)]

NUnit applies these rules:

* **The namespace of the concrete class sets the scope.** NUnit builds the setup fixture from the concrete class, so it
covers the namespace that class is in and the namespaces below it. The namespace of the base class plays no part. In
the example, the setup runs once for the tests in `SetUpFixtureInheritance.Consumer`.
* **An abstract base class is not a setup fixture by itself.** NUnit ignores abstract classes when it looks for
fixtures, so the base class does nothing until a test project derives from it. A base class in another assembly is
never scanned anyway, because NUnit only looks at the types in the test assembly. If the base class is concrete and
lives in the test assembly, it is a setup fixture for its own namespace in addition to the derived class.
* **Attributes on the derived class win.** NUnit looks for fixture attributes one level of the inheritance chain at a
time, starting at the concrete class, and uses the first level that has any. An inherited `[SetUpFixture]` is only
used when the derived class has no fixture attribute of its own:
* If the derived class is marked `[TestFixture]`, NUnit builds an ordinary test fixture and the inherited
`[SetUpFixture]` is ignored. Its tests run, and the base class `[OneTimeSetUp]` runs once for that fixture, like
any inherited `[OneTimeSetUp]`.
* If the derived class has `[Test]` methods but no `[TestFixture]`, the inherited `[SetUpFixture]` wins. NUnit builds
a setup fixture and the tests are **not discovered**. There is no warning; the tests are simply missing from the
run, and from `dotnet test --list-tests`.

[!code-csharp[SetUpFixtureInheritancePrecedence](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureInheritancePrecedence)]

> [!TIP]
> Put `[SetUpFixture]` on the concrete class as well. It is not required, but it makes the role of the class visible
> without looking at the base class, and it is easier to spot a `[Test]` method that ended up in the wrong place.

## Sharing setup across projects

When several test projects share one-time setup from a common package, a base class with `[SetUpFixture]` works, but
each project still has to declare a derived class. A `[OneTimeSetUp]` on a shared base *test* class is not an
alternative: NUnit runs it once for **every fixture** that derives from it, not once per run. See
[OneTimeSetUp](xref:attribute-onetimesetup#test-fixture-versus-setup-fixture).

The simplest pattern keeps the package free of NUnit fixture attributes. The package provides a plain class with the
setup and teardown logic:

[!code-csharp[SetUpFixtureSharingPackage](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureSharingPackage)]

Each test project declares a small `[SetUpFixture]` that calls it. Placed outside any namespace, it runs once for the
whole project:

[!code-csharp[SetUpFixtureSharingConsumer](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureSharingConsumer)]

If you cannot ask consumers to add a class, start the resource on first use from a static `Lazy<T>` in the base test
class. The resource lives as long as the type stays loaded: `dotnet test` starts a fresh test host per run, so each
test project gets its own instance, but a host that keeps the assembly loaded between runs reuses it. NUnit offers no
hook to stop it at the end of the run, so use this only for resources the process can abandon:

[!code-csharp[SetUpFixtureSharingLazy](~/snippets/Snippets.NUnit/Attributes/SetUpFixtureAttributeExamples.cs#SetUpFixtureSharingLazy)]

## Notes

1. `SetUpFixture` classes must be public and have a default constructor (or be static).
2. `SetUp` and `TearDown` attributes are not valid inside a `SetUpFixture`.
3. A setup fixture outside any namespace applies to the entire assembly.
4. Multiple setup fixtures at the same namespace level execute in indeterminate order.
5. Prior to NUnit 3.0, setup fixtures used `SetUp`/`TearDown`; NUnit 3+ uses `OneTimeSetUp`/`OneTimeTearDown`.
6. `SetUpFixture` can be inherited. The namespace of the derived class sets the scope; see [Inheritance](#inheritance).

## See Also

Expand Down
3 changes: 3 additions & 0 deletions docs/articles/nunit/writing-tests/setup-and-teardown.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ child namespaces, and its `[OneTimeTearDown]` runs once after all of them.
whole test assembly.
- The class must be public and have a default constructor, or be static.
- A setup fixture can only have `[OneTimeSetUp]` and `[OneTimeTearDown]` methods, not `[SetUp]` and `[TearDown]`.
- `[SetUpFixture]` can be inherited, and the namespace of the derived class decides what it covers. See
[Inheritance](xref:attribute-setupfixture#inheritance) on the reference page, which also shows how to share the
setup from a package used by several test projects.

## Which one should I use?

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using NUnit.Framework;
using NUnit.Framework.Api;
using NUnit.Framework.Interfaces;

#pragma warning disable CA1822

namespace Snippets.NUnit.Attributes.SampleNamespace
{
Expand All @@ -18,3 +22,243 @@ public void RunAfterAnyTests()
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureInheritance.SharedPackage
{
#region SetUpFixtureInheritanceBase
// Lives in a shared test-support package. NUnit skips abstract classes,
// so this class is not a setup fixture by itself.
[SetUpFixture]
public abstract class SharedEnvironmentSetUpFixture
{
public static bool IsStarted { get; private set; }

[OneTimeSetUp]
public void StartEnvironment()
{
IsStarted = true;
}

[OneTimeTearDown]
public void StopEnvironment()
{
IsStarted = false;
}
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureInheritance.Consumer
{
using Snippets.NUnit.Attributes.SetUpFixtureInheritance.SharedPackage;

#region SetUpFixtureInheritanceDerived
// Inherits [SetUpFixture] from the base class, so NUnit treats it as a setup
// fixture for the namespace of *this* class: ...SetUpFixtureInheritance.Consumer,
// not the namespace of the base class.
public class ConsumerEnvironment : SharedEnvironmentSetUpFixture
{
}

public class ConsumerTests
{
[Test]
public void Environment_IsStartedByInheritedSetUpFixture()
{
Assert.That(SharedEnvironmentSetUpFixture.IsStarted, Is.True);
}
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureInheritance.Precedence
{
using Snippets.NUnit.Attributes.SetUpFixtureInheritance.SharedPackage;

#region SetUpFixtureInheritancePrecedence
// [TestFixture] on the derived class wins over the inherited [SetUpFixture].
// This is an ordinary test fixture: its tests are discovered and run, and the
// base class's [OneTimeSetUp] runs once for this fixture, like any inherited
// [OneTimeSetUp].
[TestFixture]
public class DerivedTestFixture : SharedEnvironmentSetUpFixture
{
[Test]
public void ThisTestIsDiscovered()
{
Assert.That(IsStarted, Is.True);
}
}

// No fixture attribute on the derived class, so the inherited [SetUpFixture]
// wins. NUnit builds this class as a setup fixture for this namespace and
// silently ignores its [Test] methods.
public class DerivedWithTestsOnly : SharedEnvironmentSetUpFixture
{
[Test]
public void ThisTestIsNotDiscovered()
{
Assert.Fail("NUnit never runs this test.");
}
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureInheritance
{
// Verifies the rules documented on the SetUpFixture page by building the test
// tree for this assembly the same way the NUnit framework does at discovery time.
public class SetUpFixtureInheritanceVerification
{
private static ITest BuildTestTree()
{
var builder = new DefaultTestAssemblyBuilder();
return builder.Build(typeof(SetUpFixtureInheritanceVerification).Assembly, new Dictionary<string, object>());
}

private static ITest? Find(ITest root, Type type)
{
if (root.TypeInfo?.Type == type)
return root;

foreach (var child in root.Tests)
{
var found = Find(child, type);
if (found is not null)
return found;
}

return null;
}

[Test]
public void AbstractBaseClass_IsNotASetUpFixtureByItself()
{
var tree = BuildTestTree();

Assert.That(Find(tree, typeof(SharedPackage.SharedEnvironmentSetUpFixture)), Is.Null);
}

[Test]
public void ConcreteDerivedClass_IsASetUpFixture_ForItsOwnNamespace()
{
var tree = BuildTestTree();

var fixture = Find(tree, typeof(Consumer.ConsumerEnvironment));
Assert.That(fixture, Is.Not.Null);
Assert.That(fixture.TestType, Is.EqualTo("SetUpFixture"));
Assert.That(fixture.Parent?.FullName, Is.EqualTo("Snippets.NUnit.Attributes.SetUpFixtureInheritance"));
Assert.That(Find(fixture, typeof(Consumer.ConsumerTests)), Is.Not.Null);
}

[Test]
public void DerivedClassWithTestFixtureAttribute_IsATestFixture()
{
var tree = BuildTestTree();

var fixture = Find(tree, typeof(Precedence.DerivedTestFixture));
Assert.That(fixture, Is.Not.Null);
Assert.That(fixture.TestType, Is.EqualTo("TestFixture"));
Assert.That(fixture.TestCaseCount, Is.EqualTo(1));
}

[Test]
public void DerivedClassWithOnlyTestMethods_IsASetUpFixture_AndItsTestsAreNotDiscovered()
{
var tree = BuildTestTree();

var fixture = Find(tree, typeof(Precedence.DerivedWithTestsOnly));
Assert.That(fixture, Is.Not.Null);
Assert.That(fixture.TestType, Is.EqualTo("SetUpFixture"));
Assert.That(fixture.Tests.Select(t => t.Name), Does.Not.Contain(nameof(Precedence.DerivedWithTestsOnly.ThisTestIsNotDiscovered)));
}
}
}

namespace Snippets.NUnit.Attributes.SetUpFixtureSharing.SharedPackage
{
#region SetUpFixtureSharingPackage
// In the shared package: a plain class with no NUnit attributes.
public static class SharedTestEnvironment
{
public static bool IsStarted { get; private set; }

public static void Start()
{
// Start containers, seed the database, ...
IsStarted = true;
}

public static void Stop()
{
IsStarted = false;
}
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureSharing.TestProject
{
using Snippets.NUnit.Attributes.SetUpFixtureSharing.SharedPackage;

#region SetUpFixtureSharingConsumer
// In each test project that uses the package. Put it outside any namespace
// to cover the whole project.
[SetUpFixture]
public class SharedTestEnvironmentSetUp
{
[OneTimeSetUp]
public void Start()
{
SharedTestEnvironment.Start();
}

[OneTimeTearDown]
public void Stop()
{
SharedTestEnvironment.Stop();
}
}

public class OrderTests
{
[Test]
public void Environment_IsStartedOnceForTheProject()
{
Assert.That(SharedTestEnvironment.IsStarted, Is.True);
}
}
#endregion
}

namespace Snippets.NUnit.Attributes.SetUpFixtureSharing.LazyAlternative
{
public sealed class TestServer
{
public static TestServer Start() => new();

public string BaseAddress => "http://localhost:5000";
}

#region SetUpFixtureSharingLazy
// In the shared package: start the resource the first time any test needs it.
// Lazy<T> starts it once per process that loads this type, and nothing stops
// it at the end of the run.
public abstract class SharedServerTestBase
{
private static readonly Lazy<TestServer> Server = new(TestServer.Start);

protected static TestServer SharedServer => Server.Value;
}

// In a test project: no setup fixture needed.
public class CustomerTests : SharedServerTestBase
{
[Test]
public void Server_IsAvailable()
{
Assert.That(SharedServer.BaseAddress, Does.StartWith("http://"));
}
}
#endregion
}
Loading