NUnit.ApplicationDomain 11.1.0

Run NUnit tests in a separate application domain.

Install-Package NUnit.ApplicationDomain -Version 11.1.0
dotnet add package NUnit.ApplicationDomain --version 11.1.0
<PackageReference Include="NUnit.ApplicationDomain" Version="11.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add NUnit.ApplicationDomain --version 11.1.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Introduction

Sometimes when creating a library, you need to have a static initializer, property, or field. Unfortunately, testing static things isn't easy - you can't easily control when the objects are created, and you can't tell them to "uncreate" themselves so you can test it again.

[NUnit.ApplicationDomain][nuget] attempts to solve this problem by running a test in a separate app domain, isolating the static initializer to only that domain. At the end of the test, the app domain is destroyed and the next test can get a new app domain for a clean environment.

How To

First, include the NUnit.ApplicationDomain nuget package. Then decorate your test method with the RunInApplicationDomainAttribute.

RunInApplicationDomainAttribute

The RunInApplicationDomain attribute runs that test method in its separate app domain. Just put it on your test method:

#!csharp
[Test, RunInApplicationDomain]
public void MyTest()

AppDomainRunner.IsInTestAppDomain

Use the AppDomainRunner.IsInTestAppDomain method to detect if code with side-effects should be executed.
This is especially useful in the setup and teardown methods, as those methods are invoked both in the "normal"
app domain and the "test" app domain:

#!csharp
[SetUp]
public void Setup()
{
  // if we're not in the app domain, do not run the setup code
  if (!AppDomainRunner.IsInTestAppDomain)
    return;
    
  File.WriteAllText("fake.txt", "a file");
}

AppDomainRunner.DataStore

Use AppDomainRunner.DataStore as a way of passing data back and forth between the "normal" app domain and the "test" app domain:

#!csharp
internal class TestContextTests
{
  [SetUp]
  public void Setup()
  {
    // accessing TestContext.CurrentContext.TestDirectory from the app domain will throw an
    // exception but we need the test directory, so we pass it in via the data store. 
    if (!AppDomainRunner.IsInTestAppDomain)
    {
      AppDomainRunner.DataStore.Set("TestDirectory", TestContext.CurrentContext.TestDirectory);
    }
  }

  [Test, RunInApplicationDomain]
  public void VerifyItFails()
  {
    var testDirectory = AppDomainRunner.DataStore.Get<string>("TestDirectory");
    Console.WriteLine($"The test directory is: {testDirectory}");

    // we can also pass data back into the "normal" domain
    AppDomainRunner.DataStore.Set("ShouldBeSetFromAppDomain", testDirectory);
  }

  [TearDown]
  public void Teardown()
  {
    // okay, make sure everything worked properly now
    if (!AppDomainRunner.IsInTestAppDomain)
    {
      var testDirectory = AppDomainRunner.DataStore.Get<string>("ShouldBeSetFromAppDomain");

      // this should have been set by the test app-domain
      Assert.That(testDirectory, Is.EqualTo(TestContext.CurrentContext.TestDirectory));
    }
  }
}

This can be especially useful for verifying that something occurred in the app domain or for adding information that can't be accessed from the test app domain such as the members on TestContext.CurrentContext.

Gotchas

There are a couple things that you should know about the way the tests run:

  • The class containing the test method must be public
  • Only the test method, the setup method, and the test method will be called. Any extra NUnit parameters (such as ExpectedException, or RequiresSTA) will not be honored (if you want/needs support of this, create an issue).
  • The setup and teardown methods are invoked both normally and in the app domain. This results in the setup and teardown methods being called twice. It is advised to use AppDomainRunner.IsInTestAppDomain property to mitigate this problem.

Tests Returning Tasks

NUnit 3.0 introduced [better] support for async tests (tests that return a Task). The way NUnit.ApplicationDomain is implemented requires the library to somehow block until the test is completed before running the TearDown. It does this [by default] by invoking .Wait() on the returned task.

If you need to block via some other mechnism (for example using a Dispatcher, or pumping some sort of message thread) you can implement IAsyncTestResultHandler on your test class and then block using your own mechnism:

#!csharp
[Test]
public async Task WaitsForDispatcherToContinue()
{
  // pretend that for some made-up reason, we need to be in the event loop 
  await Dispatcher.Yield();

  // and pretend that something later triggers that allows us to complete
  await Task.Delay(TimeSpan.FromSeconds(3));

  AppDomainRunner.DataStore.Set<bool>("ran", true);
}

[TearDown]
public void Teardown()
{
  Assert.That(AppDomainRunner.DataStore.Get<bool>("ran"), Is.True);
}

/// <inheritdoc />
void IAsyncTestResultHandler.Process(Task task)
{
  // if we just simply did task.Wait(), we would block indefinitely because no-one is message
  // pumping. 

  // instead, tell the dispatcher to run until the task has resolved
  var frame = new DispatcherFrame();
  task.ContinueWith(_1 => frame.Continue = false);
  Dispatcher.PushFrame(frame);

  // propagate any exceptions
  task.Wait();
}

Full test/example is implemented as a test.

Introduction

Sometimes when creating a library, you need to have a static initializer, property, or field. Unfortunately, testing static things isn't easy - you can't easily control when the objects are created, and you can't tell them to "uncreate" themselves so you can test it again.

[NUnit.ApplicationDomain][nuget] attempts to solve this problem by running a test in a separate app domain, isolating the static initializer to only that domain. At the end of the test, the app domain is destroyed and the next test can get a new app domain for a clean environment.

How To

First, include the NUnit.ApplicationDomain nuget package. Then decorate your test method with the RunInApplicationDomainAttribute.

RunInApplicationDomainAttribute

The RunInApplicationDomain attribute runs that test method in its separate app domain. Just put it on your test method:

#!csharp
[Test, RunInApplicationDomain]
public void MyTest()

AppDomainRunner.IsInTestAppDomain

Use the AppDomainRunner.IsInTestAppDomain method to detect if code with side-effects should be executed.
This is especially useful in the setup and teardown methods, as those methods are invoked both in the "normal"
app domain and the "test" app domain:

#!csharp
[SetUp]
public void Setup()
{
  // if we're not in the app domain, do not run the setup code
  if (!AppDomainRunner.IsInTestAppDomain)
    return;
    
  File.WriteAllText("fake.txt", "a file");
}

AppDomainRunner.DataStore

Use AppDomainRunner.DataStore as a way of passing data back and forth between the "normal" app domain and the "test" app domain:

#!csharp
internal class TestContextTests
{
  [SetUp]
  public void Setup()
  {
    // accessing TestContext.CurrentContext.TestDirectory from the app domain will throw an
    // exception but we need the test directory, so we pass it in via the data store. 
    if (!AppDomainRunner.IsInTestAppDomain)
    {
      AppDomainRunner.DataStore.Set("TestDirectory", TestContext.CurrentContext.TestDirectory);
    }
  }

  [Test, RunInApplicationDomain]
  public void VerifyItFails()
  {
    var testDirectory = AppDomainRunner.DataStore.Get<string>("TestDirectory");
    Console.WriteLine($"The test directory is: {testDirectory}");

    // we can also pass data back into the "normal" domain
    AppDomainRunner.DataStore.Set("ShouldBeSetFromAppDomain", testDirectory);
  }

  [TearDown]
  public void Teardown()
  {
    // okay, make sure everything worked properly now
    if (!AppDomainRunner.IsInTestAppDomain)
    {
      var testDirectory = AppDomainRunner.DataStore.Get<string>("ShouldBeSetFromAppDomain");

      // this should have been set by the test app-domain
      Assert.That(testDirectory, Is.EqualTo(TestContext.CurrentContext.TestDirectory));
    }
  }
}

This can be especially useful for verifying that something occurred in the app domain or for adding information that can't be accessed from the test app domain such as the members on TestContext.CurrentContext.

Gotchas

There are a couple things that you should know about the way the tests run:

  • The class containing the test method must be public
  • Only the test method, the setup method, and the test method will be called. Any extra NUnit parameters (such as ExpectedException, or RequiresSTA) will not be honored (if you want/needs support of this, create an issue).
  • The setup and teardown methods are invoked both normally and in the app domain. This results in the setup and teardown methods being called twice. It is advised to use AppDomainRunner.IsInTestAppDomain property to mitigate this problem.

Tests Returning Tasks

NUnit 3.0 introduced [better] support for async tests (tests that return a Task). The way NUnit.ApplicationDomain is implemented requires the library to somehow block until the test is completed before running the TearDown. It does this [by default] by invoking .Wait() on the returned task.

If you need to block via some other mechnism (for example using a Dispatcher, or pumping some sort of message thread) you can implement IAsyncTestResultHandler on your test class and then block using your own mechnism:

#!csharp
[Test]
public async Task WaitsForDispatcherToContinue()
{
  // pretend that for some made-up reason, we need to be in the event loop 
  await Dispatcher.Yield();

  // and pretend that something later triggers that allows us to complete
  await Task.Delay(TimeSpan.FromSeconds(3));

  AppDomainRunner.DataStore.Set<bool>("ran", true);
}

[TearDown]
public void Teardown()
{
  Assert.That(AppDomainRunner.DataStore.Get<bool>("ran"), Is.True);
}

/// <inheritdoc />
void IAsyncTestResultHandler.Process(Task task)
{
  // if we just simply did task.Wait(), we would block indefinitely because no-one is message
  // pumping. 

  // instead, tell the dispatcher to run until the task has resolved
  var frame = new DispatcherFrame();
  task.ContinueWith(_1 => frame.Continue = false);
  Dispatcher.PushFrame(frame);

  // propagate any exceptions
  task.Wait();
}

Full test/example is implemented as a test.

Release Notes

## Version 11.1.0
         - Fix: Add broader support for newer versions of NUnit (Brandon Ording)

         ## Version 11.0.0
         - Add support for (and a dependency on) NUnit 3.7.0

         ## Version 10.2.0
         - Add support for app-domain factories, allowing the constructed app-domain to
         be customized.

         ## Version 10.1.0
         - Add support for parameterized test fixtures (rubenhak)

         ## Version 10.0.0
         - Add waiting for Task-returning tests to complete
         - Add ability to customize behavior of task-returning tests

         ## Version 9.0.0
         - Fix: Unload test app-domains after use in order to lower resource usage (John Lewin)

         ## Version 8.0.0
         - Fix: Always run all teardown methods, even when the test method threw an exception
         - Add the ability to share data between the parent-domain and the test domain
         via AppDomainRunner.DataStore

         ## Version 7.0.0
         - Strong name the assembly
         - Add support for Mono (based on changes from Frederik Carlier)

         ## Version 6.0.0
         - Support NUnit 3.0, Drop NUnit 2 support

         ## Version 5.0.1
         - Architecture change to separate out the internal and public api
         - Allow app-domain test failures logging to be suppressed
         (AppDomainRunner.ShouldIncludeAppDomainErrorMessages)
         - Removed obsolete members
         - AppDomainTestRunnerBase removed
         - non-empty RunInApplicationDomainAttribute ctor removed

         ## Version 4.0.1
         - Add support for additional test runners/shadow copying

         ## Version 3.0.1
         - Add support for teardown methods
         - Add official support for multiple setup/teardown methods
         - Add AppDomainRunner.IsInTestAppDomain to detect if you're running in
         the test app domain

         ## Version 2.0.0
         - Find assembly location by escaping assembly codebases

         ## Version 1.0.3
         - Added support for abstract classes

         ## Version 1.0.1
         Initial Release

  • .NETFramework 4.0

Showing the top 3 GitHub repositories that depend on NUnit.ApplicationDomain:

Repository Stars
Particular/NServiceBus
The most popular service bus for .NET
MatterHackers/MatterControl
3D printing software for Windows, Mac and Linux
MatterHackers/agg-sharp
A C# port of Anti-Grain Geometry (AGG) with extensions for common scenarios

Version History

Version Downloads Last updated
11.1.0 13,087 7/18/2018
11.0.0 13,898 7/27/2017
10.2.0 6,132 12/4/2016
10.1.0-beta1 283 10/18/2016
10.0.0 3,299 10/15/2016
10.0.0-beta1 330 10/15/2016
9.0.0 875 10/4/2016
8.0.0 887 8/29/2016
7.0.0 733 4/2/2016
6.0.0 1,010 12/5/2015
5.0.1 5,538 4/29/2015
4.0.1 472 4/4/2015
3.0.1 865 11/24/2014
3.0.0 390 11/24/2014
2.0.1 598 10/11/2014
2.0.0 429 10/11/2014
1.0.3 510 8/20/2014
1.0.2 684 12/13/2013
1.0.1 578 6/22/2013