Custom Attributes
NUnit implements a great deal of its functionality in its attributes. This functionality is accessed through a number of standard interfaces, which are implemented by the attributes. Users may create their own attributes by implementing these interfaces.
The interfaces fall into two groups, according to the stage in the life-cycle of a test at which NUnit calls them: load time and execution time. The grouping is more than a convenience. Which group an interface belongs to decides what an attribute implementing it can and cannot do, so it pays to understand the split before choosing an interface. Load Time and Execution Time explains the split from the test writer's point of view. This page covers what it means for attribute authors.
Load-Time Interfaces
Loading tests means loading the assembly into memory and examining its content to discover the classes and methods that represent tests. NUnit builds its internal representation of every test at this time, including the arguments of every test case. If requested by the runner, information about the tests is returned for display, as the Visual Studio Test Explorer and the NUnit GUI do.
The following interfaces are called at load time.
| Interface | Used By |
|---|---|
| IFixtureBuilder | Attributes that know how to build a fixture from a test class |
| ITestBuilder | Attributes that know how to build one or more parameterized test cases for a method |
| ISimpleTestBuilder | Attributes that know how to build a single non-parameterized test case for a method |
| IParameterDataSource | Attributes that supply values for a single parameter for use in generating test cases |
| IImplyFixture | Attributes used on a method to signal that the defining class should be treated as a fixture |
| IApplyToTest | Attributes that make modifications to a test immediately after it is constructed |
Load-time code decides what the tests are: which tests exist, what they are called, which arguments they receive, and which properties, categories and run states they carry. Everything it decides is final once loading completes.
Load-time code cannot:
- Affect a running test directly. No load-time interface is called again once the tests are built. Whatever the
test needs at execution time must already be stored on the
Testobject, in its arguments or in itsProperties, or be created at execution time by one of the interfaces below. - Rely on execution-time state. Nothing created by fixture constructors,
[OneTimeSetUp],[SetUpFixture]or[SetUp]methods exists yet. Those all run later. - Hand live objects to the execution phase. An object a builder creates and passes as an argument is created at load time. The test receives that same object, not a copy, but if the runner loads the assembly more than once the builder runs again and creates a new one. Anything else that was attached to the first object, such as an event subscription made elsewhere, is not attached to the second.
Execution-Time Interfaces
At execution time, some or all of the tests that were previously loaded are actually run. Their results are returned and made available to the runner.
The following interfaces are called at execution time.
| Interface | Used By |
|---|---|
| IApplyToContext | Attributes that set up the context prior to execution |
| ICommandWrapper | Attributes that can wrap a TestCommand with another command |
Action Attributes and Execution Hooks also run at execution time.
Execution-time code is the right place for anything that must be alive while the test runs: event subscriptions,
listeners, connections, shared services and anything else with a lifetime. It has access to the test's arguments and
properties, which load-time code prepared for it, and to the TestExecutionContext.
Rules for attribute authors
- Do not keep state in attribute instances. NUnit obtains attributes through reflection, and it may ask for them
more than once. Each request creates new attribute instances. An attribute that implements both
ITestBuilderandIApplyToTest, for example, can be called on two different instances: one when the tests are built and another when attributes are applied to them. A field set inBuildFromis then not visible inApplyToTest. How often instances are created varies between NUnit versions, so never depend on it. Put what a test needs on theTestobject itself, in its arguments or itsProperties, or resolve it at execution time. - Builders must be repeatable. A builder or data source must produce the same tests, with the same names and
arguments, every time it runs. Runners match tests between loads by name, and some runners load the assembly more
than once per run. Do not use
Random, the current time, or counters and other static side effects. - Create live objects at execution time. Have the builder supply data that describes what the test needs, such
as a key or a factory delegate, and have an execution-time interface, an action attribute or the test's own setup
create or resolve the object. A
Func<T>orLazy<T>passed as an argument is a good way to defer creation until the test runs. - Do not assume the assembly is loaded once. The Visual Studio adapter can load it once to discover tests and again, in another process, to run them. See Discovery and execution in the adapter.