Umbraco.Community.Integration.Tests.Extensions 1.0.0-beta005

This is a prerelease version of Umbraco.Community.Integration.Tests.Extensions.
dotnet add package Umbraco.Community.Integration.Tests.Extensions --version 1.0.0-beta005
                    
NuGet\Install-Package Umbraco.Community.Integration.Tests.Extensions -Version 1.0.0-beta005
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Umbraco.Community.Integration.Tests.Extensions" Version="1.0.0-beta005" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Umbraco.Community.Integration.Tests.Extensions" Version="1.0.0-beta005" />
                    
Directory.Packages.props
<PackageReference Include="Umbraco.Community.Integration.Tests.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Umbraco.Community.Integration.Tests.Extensions --version 1.0.0-beta005
                    
#r "nuget: Umbraco.Community.Integration.Tests.Extensions, 1.0.0-beta005"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Umbraco.Community.Integration.Tests.Extensions@1.0.0-beta005
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Umbraco.Community.Integration.Tests.Extensions&version=1.0.0-beta005&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Umbraco.Community.Integration.Tests.Extensions&version=1.0.0-beta005&prerelease
                    
Install as a Cake Tool

NUnit Composition with Umbraco

NuGet Version

Installation

dotnet add package Umbraco.Community.Integration.Tests.Extensions --version 1.0.0-beta003

Overview / Problem statement

This package has an [InjectionSource] and an [Inject] attribute that may be used to enrich any NUnit suite, but the [MakeOneTimeLifecycle] attribute is mainly targeted at Umbraco.

Umbraco publishes their own tests as NuGet packages for implementors to re-use instead of writing tons of setup code.

However, the Umbraco base classes set up and tear down the Umbraco instance for each test.
They may set up the database once per fixture, but each test gets a fresh Umbraco instance.
You'll have to track whether your own schema is created in your own setup method.

This is a problem if you want to run a large number of tests that all need the same schema.
This repository contains an example of how to work around this by "hacking" the NUnit pipeline and modifying the UmbracoIntegrationTest setup and teardown methods to be onetime variants.

Once the lifecycle is modified to run as one-time variants, we can utilize the [Inject] attribute to inject services from the Umbraco ServiceProvider to child tests in the scope.

Important attributes

The tern "scope fixture" is used to descibe an NUnit fixture with only OneTimeSetUp and OneTimeTearDown methods.

Attribute Description Dependencies
[ExtendableSetUpFixture] Replacement for [SetUpFixture] Allows IApplyToTest implementations to mutate the lifecycle methods via IExtendableLifecycle. Allows for further extension attributes to "get to do more". NUnit
[MakeOneTimeLifecycle] Moves [SetUp] and [TearDown] methods to one-time lifecycle. Necessary to use UmbracoIntegrationTest and others as base classes for scoped setup fixtures. NUnit
[InjectionSource] Allows a scope fixture to expose an IServiceProvider instance to child tests. NUnit
[Inject] Allows a test fixture to receive services from the closest [InjectionSource] in the hierarchy. NUnit
[ReusableDatabase] Marks a scope fixture to re-use the database schema and seed data for child tests. Umbraco

Dependency Injection Example

[InjectionProvider(nameof(Services))]
public class ScopeFixture
{
    public IServiceProvider Services { get; }

    public ScopeFixture()
    {
        Services = new ServiceCollection()
            .AddSingleton<IFooService, FooService>()
            .AddSingleton<IBarService, BarService>()
            .BuildServiceProvider();
    }
}

[Inject(nameof(Inject))]
public class SomeTests
{
    private IFooService fooService = null!;
    private IBarService barService = null!;
    public void Inject(IFooService fooService, IBarService barService)
    {
        this.fooService = fooService;
        this.barService = barService;
    }
    [Test]
    public void TestUsingInjectedServices()
    {
        Assert.That(fooService.DoFoo(), Is.EqualTo("Foo"));
        Assert.That(barService.DoBar(), Is.EqualTo("Bar"));
    }
}

Umbraco Example (Per-test scoped base class from library)

There are two example "scoped" sets of tests in the FeatureA and FeatureB folders of the UmbracoTestsComposition project.

As with NUnit SetUpFixtures, the extandable setup fixture must be in the root namespace of the tests that need it.
Because there's a lot of singletons and stuff in Umbraco, it is likely not possible, and at least not recommended to have more than one scoped setup fixture per namespace, and no further ones in child namespaces.
As of writing Umbraco's base classes already specify [SingleThreaded] and [NonParallelizable].

An Umbraco-scoped setup fixture

namespace FeatureA;

[UmbracoTest(
    Database = UmbracoTestOptions.Database.NewSchemaPerFixture, 
    Logger = UmbracoTestOptions.Logger.Console
)]
[ExtendableSetUpFixture]
[MakeOneTimeLifecycle(
    [nameof(Setup), nameof(SetUp_Logging)],
    [nameof(TearDown), nameof(TearDownAsync), nameof(FixtureTearDown), nameof(TearDown_Logging)]
)]
[InjectionProvider(nameof(Services))]
public class FeatureAScope : UmbracoIntegrationTest
{
    public void StubForUmbracoTestDiscovery() {}

    public FeatureAScope()
    {
        // Umbraco's TestOptionAttributeBase looks for the UmbracoTest attribute via the current test method or its declaring type.
        // We need to set a dummy test method from this exact setup fixture.
        // It could possibly be done by sneaking it in to the first instance of onetime setups, but we still need a declared method on this type.
        this.ExposeUmbracoTestAttribute(nameof(StubForUmbracoTestDiscovery));
    }
}

A couple of test fixtures with access to the scoped setup fixture

[Inject(nameof(Inject))]
public class FeatureATests
{
    private IDataTypeService dataTypeService = null!;

    public void Inject(IDataTypeService dataTypeService)
    {
        this.dataTypeService = dataTypeService;
    }

    [Test]
    public async Task CanGetDataTypeFromInjectedService()
    {
        var allTypes = (await dataTypeService.GetAllAsync()).Take(3).ToList();
        Console.WriteLine($"We've got data types like {String.Join(',', allTypes.Select(x => x.Name))}...");
        Assert.That(allTypes, Has.Count.GreaterThan(0));
    }
}

[Inject(nameof(Inject))]
public class FeatureATestUsingSeveralServices
{
    private IDataTypeService dataTypeService = null!;
    private DataEditorCollection editorCollection = null!;
    private IConfigurationEditorJsonSerializer editorSerializer = null!;

    public void Inject(IDataTypeService dataTypeService, DataEditorCollection editorCollection, IConfigurationEditorJsonSerializer editorSerializer)
    {
        this.dataTypeService = dataTypeService;
        this.editorCollection = editorCollection;
        this.editorSerializer = editorSerializer;
    }

    [Test]
    public async Task CanCreateDataTypeUsingAllNecessaryServices()
    {
        var textBoxEditor = editorCollection.Single(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
        var result = await dataTypeService.CreateAsync(
            new DataType(textBoxEditor, editorSerializer)
            {
                Name = "A test datatype"
            }, 
            Constants.Security.SuperUserKey);

        Assert.That(result.Success, Is.True, () => $"Failed with status {result.Status} and exception message {result.Exception?.Message ?? "<No exception thrown>"}");
    }
}

Snippets for re-usability with Umbraco

OneTimeUmbracoSetUp

public class OneTimeUmbracoSetUpAttribute() : MakeOneTimeLifecycleAttribute(
    [nameof(UmbracoIntegrationTest.Setup), nameof(UmbracoIntegrationTest.SetUp_Logging)],
    [nameof(UmbracoIntegrationTest.TearDown), nameof(UmbracoIntegrationTest.TearDownAsync), nameof(UmbracoIntegrationTest.FixtureTearDown), nameof(UmbracoIntegrationTest.TearDown_Logging)]
)
{
}

ServiceProviderAttribute

public class ServiceProviderAttribute() : InjectionProviderAttribute(nameof(IHost.Services)) { }

A basic scoped Umbraco instance

[UmbracoTest(
    Database = UmbracoTestOptions.Database.NewSchemaPerFixture, 
    Logger = UmbracoTestOptions.Logger.Console
)]
[ExtendableSetUpFixture]
[OneTimeUmbracoSetUp]
[ServiceProvider]
public class FeatureAScope : UmbracoIntegrationTest
{
}

A base class for scoped Umbraco tests

[UmbracoTest(
    Database = UmbracoTestOptions.Database.NewSchemaPerFixture,
    Logger = UmbracoTestOptions.Logger.Console
)]
[ExtendableSetUpFixture]
[OneTimeUmbracoSetUp]
[ServiceProvider]
public abstract class ScopedUmbracoIntegrationTest : UmbracoIntegrationTest
{
}

Hopes and dreams

With Umbraco

  • Attribute for scope hierarchies utilizing ICoreScopeProvider and IServiceScope
  • Attribute transactions such that each test fixture or test can commit or roll back changes as needed
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta005 38 11/24/2025
1.0.0-beta004 44 11/24/2025
1.0.0-beta003 35 11/24/2025