From 76cda7fd0cc758c1ad15bfd5c94932b18da83e22 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 14:24:45 +0000 Subject: [PATCH 1/4] Fix issue running UmbracoTestServerTestBase tests with debugger attached Ensure ConfigureWebServer called before ConfigureServices for tests that make use of WebApplicationFactory. Still not apparent why tests would pass if debugger isn't attached? --- .../UmbracoTestServerTestBase.cs | 70 +++++++++++++------ .../Testing/UmbracoIntegrationTest.cs | 14 ++-- .../Events/EventAggregatorTests.cs | 3 +- .../UmbracoExamine/ExamineBaseTest.cs | 7 +- 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index bd9418bdb6..45fb305de9 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -12,13 +12,16 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; @@ -77,28 +80,55 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest LinkGenerator = Factory.Services.GetRequiredService(); } - public override IHostBuilder CreateHostBuilder() + protected override IHostBuilder CreateHostBuilder() { - IHostBuilder builder = base.CreateHostBuilder(); - builder.ConfigureWebHost(builder => - { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); + /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup + * found in Program.cs and avoids nasty surprises. + * + * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered + * at the point in time that the service collection is snapshotted. + */ + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + configBuilder.Sources.Clear(); + configBuilder.AddInMemoryCollection(InMemoryConfiguration); - // call startup - builder.Configure(app => Configure(app)); - }) - .UseDefaultServiceProvider(cfg => - { - // These default to true *if* WebHostEnvironment.EnvironmentName == Development - // When running tests, EnvironmentName used to be null on the mock that we register into services. - // Enable opt in for tests so that validation occurs regardless of environment name. - // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. - cfg.ValidateOnBuild = true; - cfg.ValidateScopes = true; - }); + Configuration = configBuilder.Build(); + }) + .ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - return builder; + // call startup + builder.Configure(app => Configure(app)); + }) + .ConfigureServices((_, services) => + { + ConfigureServices(services); + ConfigureTestSpecificServices(services); + services.AddUnique(CreateLoggerFactory()); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }) + .UseDefaultServiceProvider(cfg => + { + // These default to true *if* WebHostEnvironment.EnvironmentName == Development + // When running tests, EnvironmentName used to be null on the mock that we register into services. + // Enable opt in for tests so that validation occurs regardless of environment name. + // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. + cfg.ValidateOnBuild = true; + cfg.ValidateScopes = true; + }); + + return hostBuilder; } /// @@ -157,7 +187,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected WebApplicationFactory Factory { get; private set; } - public override void ConfigureServices(IServiceCollection services) + private void ConfigureServices(IServiceCollection services) { services.AddTransient(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 7ec8a7fc14..915d2d7629 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -128,7 +128,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing UseTestDatabase(Services); } - private ILoggerFactory CreateLoggerFactory() + protected ILoggerFactory CreateLoggerFactory() { try { @@ -163,7 +163,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// - public virtual IHostBuilder CreateHostBuilder() + protected virtual IHostBuilder CreateHostBuilder() { IHostBuilder hostBuilder = Host.CreateDefaultBuilder() @@ -180,9 +180,10 @@ namespace Umbraco.Cms.Tests.Integration.Testing Configuration = configBuilder.Build(); }) - .ConfigureServices((hostContext, services) => + .ConfigureServices((_, services) => { ConfigureServices(services); + ConfigureTestSpecificServices(services); services.AddUnique(CreateLoggerFactory()); if (!TestOptions.Boot) @@ -192,10 +193,15 @@ namespace Umbraco.Cms.Tests.Integration.Testing services.AddUnique(Mock.Of()); } }); + return hostBuilder; } - public virtual void ConfigureServices(IServiceCollection services) + protected virtual void ConfigureTestSpecificServices(IServiceCollection services) + { + } + + private void ConfigureServices(IServiceCollection services) { services.AddSingleton(TestHelper.DbProviderFactoryCreator); services.AddTransient(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs index 6446bf542a..55429af147 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs @@ -14,9 +14,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events [TestFixture] public class EventAggregatorTests : UmbracoTestServerTestBase { - public override void ConfigureServices(IServiceCollection services) + protected override void ConfigureTestSpecificServices(IServiceCollection services) { - base.ConfigureServices(services); services.AddScoped(); services.AddTransient, EventAggregatorTestNotificationHandler>(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index bc1c6b8f02..7769f99d83 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -29,11 +29,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); - public override void ConfigureServices(IServiceCollection services) - { - base.ConfigureServices(services); - services.AddSingleton(); - } + protected override void ConfigureTestSpecificServices(IServiceCollection services) + => services.AddSingleton(); /// /// Used to create and manage a testable index From c1fddd98be5e42ed49ce6f21eec18f9ac4070812 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Feb 2022 20:37:56 +0000 Subject: [PATCH 2/4] Additional refactoring I have been meaning to get around to for a while. --- .../UmbracoBuilderExtensions.cs | 4 +- .../UmbracoTestServerTestBase.cs | 140 ++++---- .../UmbracoWebApplicationFactory.cs | 6 +- .../Testing/UmbracoIntegrationTest.cs | 316 +----------------- .../Testing/UmbracoIntegrationTestBase.cs | 243 ++++++++++++++ .../Umbraco.Core/RuntimeStateTests.cs | 9 +- .../Scoping/ScopeTests.cs | 9 +- .../Scoping/ScopedRepositoryTests.cs | 6 +- .../Controllers/EntityControllerTests.cs | 3 + 9 files changed, 349 insertions(+), 387 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 7bf435485f..b5c917a337 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -39,9 +39,9 @@ namespace Umbraco.Cms.Tests.Integration.DependencyInjection /// /// Uses/Replaces services with testing services /// - public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper, AppCaches appCaches = null) + public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper) { - builder.Services.AddUnique(appCaches ?? AppCaches.NoCache); + builder.Services.AddUnique(AppCaches.NoCache); builder.Services.AddUnique(Mock.Of()); builder.Services.AddUnique(testHelper.MainDom); diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 45fb305de9..6d7835b889 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -1,6 +1,3 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - using System; using System.Linq.Expressions; using System.Net.Http; @@ -17,7 +14,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; @@ -35,14 +31,17 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = true)] - public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest + public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTestBase { - [SetUp] - public override void Setup() - { - InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = null; - InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true"; + protected HttpClient Client { get; private set; } + protected LinkGenerator LinkGenerator { get; private set; } + + protected WebApplicationFactory Factory { get; private set; } + + [SetUp] + public void Setup() + { /* * It's worth noting that our usage of WebApplicationFactory is non-standard, * the intent is that your Startup.ConfigureServices is called just like @@ -53,9 +52,12 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest * This is currently a pain to refactor towards due to UmbracoBuilder+TypeFinder+TypeLoader setup but * we should get there one day. * + * However we need to separate the testing framework we provide for downstream projects from our own tests. + * We cannot use the Umbraco.Web.UI startup yet as that is not available downstream. + * * See https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests */ - var factory = new UmbracoWebApplicationFactory(CreateHostBuilder, BeforeHostStart); + var factory = new UmbracoWebApplicationFactory(CreateHostBuilder); // additional host configuration for web server integration tests Factory = factory.WithWebHostBuilder(builder => @@ -71,7 +73,6 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .AddScheme(TestAuthHandler.TestAuthenticationScheme, options => { })); }); - Client = Factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false @@ -80,57 +81,6 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest LinkGenerator = Factory.Services.GetRequiredService(); } - protected override IHostBuilder CreateHostBuilder() - { - /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup - * found in Program.cs and avoids nasty surprises. - * - * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered - * at the point in time that the service collection is snapshotted. - */ - IHostBuilder hostBuilder = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration((context, configBuilder) => - { - context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); - configBuilder.Sources.Clear(); - configBuilder.AddInMemoryCollection(InMemoryConfiguration); - - Configuration = configBuilder.Build(); - }) - .ConfigureWebHost(builder => - { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - - // call startup - builder.Configure(app => Configure(app)); - }) - .ConfigureServices((_, services) => - { - ConfigureServices(services); - ConfigureTestSpecificServices(services); - services.AddUnique(CreateLoggerFactory()); - - if (!TestOptions.Boot) - { - // If boot is false, we don't want the CoreRuntime hosted service to start - // So we replace it with a Mock - services.AddUnique(Mock.Of()); - } - }) - .UseDefaultServiceProvider(cfg => - { - // These default to true *if* WebHostEnvironment.EnvironmentName == Development - // When running tests, EnvironmentName used to be null on the mock that we register into services. - // Enable opt in for tests so that validation occurs regardless of environment name. - // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. - cfg.ValidateOnBuild = true; - cfg.ValidateScopes = true; - }); - - return hostBuilder; - } - /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. @@ -139,7 +89,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected string PrepareApiControllerUrl(Expression> methodSelector) where T : UmbracoApiController { - string url = LinkGenerator.GetUmbracoApiService(methodSelector); + var url = LinkGenerator.GetUmbracoApiService(methodSelector); return PrepareUrl(url); } @@ -151,7 +101,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected string PrepareSurfaceControllerUrl(Expression> methodSelector) where T : SurfaceController { - string url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector); + var url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector); return PrepareUrl(url); } @@ -181,17 +131,67 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest return url; } - protected HttpClient Client { get; private set; } + private IHostBuilder CreateHostBuilder() + { + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + configBuilder.Sources.Clear(); + configBuilder.AddInMemoryCollection(InMemoryConfiguration); - protected LinkGenerator LinkGenerator { get; private set; } + Configuration = configBuilder.Build(); + }) + /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup + * found in Program.cs and avoids nasty surprises. + * + * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered + * at the point in time that the service collection is snapshotted. + */ + .ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); - protected WebApplicationFactory Factory { get; private set; } + // call startup + builder.Configure(Configure); + }) + .ConfigureServices((_, services) => + { + ConfigureServices(services); + ConfigureTestSpecificServices(services); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }) + .UseDefaultServiceProvider(cfg => + { + // These default to true *if* WebHostEnvironment.EnvironmentName == Development + // When running tests, EnvironmentName used to be null on the mock that we register into services. + // Enable opt in for tests so that validation occurs regardless of environment name. + // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. + cfg.ValidateOnBuild = true; + cfg.ValidateScopes = true; + }); + + return hostBuilder; + } + + protected virtual IServiceProvider Services => Factory.Services; + + protected virtual T GetRequiredService() => Factory.Services.GetRequiredService(); private void ConfigureServices(IServiceCollection services) { + services.AddUnique(CreateLoggerFactory()); services.AddTransient(); Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); + TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, hostingEnvironment, @@ -234,8 +234,10 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .Build(); } - public override void Configure(IApplicationBuilder app) + private void Configure(IApplicationBuilder app) { + UseTestDatabase(app); + app.UseUmbraco() .WithMiddleware(u => { diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs index 380603ae5c..62d84a19a1 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs @@ -18,11 +18,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest /// /// Method to create the IHostBuilder /// Method to perform an action before IHost starts - public UmbracoWebApplicationFactory(Func createHostBuilder, Action beforeStart = null) - { - _createHostBuilder = createHostBuilder; - _beforeStart = beforeStart; - } + public UmbracoWebApplicationFactory(Func createHostBuilder) => _createHostBuilder = createHostBuilder; protected override IHostBuilder CreateHostBuilder() => _createHostBuilder(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 915d2d7629..1e9ae97474 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -1,23 +1,12 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - using System; -using System.Collections.Generic; -using System.Data.Common; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; -using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; @@ -29,14 +18,11 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.DependencyInjection; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; -using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; using Umbraco.Cms.Tests.Integration.Extensions; -using Umbraco.Cms.Tests.Integration.Implementations; using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Testing @@ -47,123 +33,35 @@ namespace Umbraco.Cms.Tests.Integration.Testing /// /// This will use a Host Builder to boot and install Umbraco ready for use /// - [SingleThreaded] - [NonParallelizable] - public abstract class UmbracoIntegrationTest + public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase { - private List _testTeardown = null; - private readonly List _fixtureTeardown = new List(); + private IHost _host; - public void OnTestTearDown(Action tearDown) - { - if (_testTeardown == null) - { - _testTeardown = new List(); - } - - _testTeardown.Add(tearDown); - } - - public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); - - [OneTimeTearDown] - public void FixtureTearDown() - { - foreach (Action a in _fixtureTeardown) - { - a(); - } - } - - [TearDown] - public async Task TearDownAsync() - { - if (_testTeardown != null) - { - foreach (Action a in _testTeardown) - { - a(); - } - } - - _testTeardown = null; - _firstTestInFixture = false; - s_firstTestInSession = false; - - // Ensure CoreRuntime stopped (now it's a HostedService) - IHost host = Services?.GetService(); - if (host is not null) - { - await host.StopAsync(); - host.Dispose(); - } - - } - - [TearDown] - public virtual void TearDown_Logging() => - TestContext.Progress.Write($" {TestContext.CurrentContext.Result.Outcome.Status}"); + protected IServiceProvider Services => _host.Services; [SetUp] - public virtual void SetUp_Logging() => - TestContext.Progress.Write($"Start test {s_testCount++}: {TestContext.CurrentContext.Test.Name}"); - - [SetUp] - public virtual void Setup() + public void Setup() { InMemoryConfiguration[Constants.Configuration.ConfigUnattended + ":" + nameof(UnattendedSettings.InstallUnattended)] = "true"; IHostBuilder hostBuilder = CreateHostBuilder(); - IHost host = hostBuilder.Build(); - BeforeHostStart(host); - host.Start(); + _host = hostBuilder.Build(); + UseTestDatabase(_host.Services); + _host.Start(); - var app = new ApplicationBuilder(host.Services); - Configure(app); - } - - protected virtual void BeforeHostStart(IHost host) - { - Services = host.Services; - UseTestDatabase(Services); - } - - protected ILoggerFactory CreateLoggerFactory() - { - try + if (TestOptions.Boot) { - switch (TestOptions.Logger) - { - case UmbracoTestOptions.Logger.Mock: - return NullLoggerFactory.Instance; - case UmbracoTestOptions.Logger.Serilog: - return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => - { - string path = Path.Combine(TestHelper.WorkingDirectory, "logs", "umbraco_integration_tests_.txt"); - - Log.Logger = new LoggerConfiguration() - .WriteTo.File(path, rollingInterval: RollingInterval.Day) - .MinimumLevel.Debug() - .CreateLogger(); - - builder.AddSerilog(Log.Logger); - }); - case UmbracoTestOptions.Logger.Console: - return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); - } + Services.GetRequiredService().EnsureUmbracoContext(); } - catch - { - // ignored - } - - return NullLoggerFactory.Instance; } + [TearDown] + public void TearDownAsync() => _host.StopAsync(); + /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// - protected virtual IHostBuilder CreateHostBuilder() + private IHostBuilder CreateHostBuilder() { IHostBuilder hostBuilder = Host.CreateDefaultBuilder() @@ -171,7 +69,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing // if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will // create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves, // and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly. - // .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); }) .ConfigureAppConfiguration((context, configBuilder) => { context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); @@ -184,7 +81,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing { ConfigureServices(services); ConfigureTestSpecificServices(services); - services.AddUnique(CreateLoggerFactory()); if (!TestOptions.Boot) { @@ -197,12 +93,9 @@ namespace Umbraco.Cms.Tests.Integration.Testing return hostBuilder; } - protected virtual void ConfigureTestSpecificServices(IServiceCollection services) - { - } - private void ConfigureServices(IServiceCollection services) { + services.AddUnique(CreateLoggerFactory()); services.AddSingleton(TestHelper.DbProviderFactoryCreator); services.AddTransient(); IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); @@ -232,7 +125,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing .AddBackOfficeIdentity() .AddMembersIdentity() .AddExamine() - .AddTestServices(TestHelper, GetAppCaches()); + .AddTestServices(TestHelper); if (TestOptions.Mapper) { @@ -250,182 +143,12 @@ namespace Umbraco.Cms.Tests.Integration.Testing builder.Build(); } - protected virtual AppCaches GetAppCaches() => - - // Disable caches for integration tests - AppCaches.NoCache; - - public virtual void Configure(IApplicationBuilder app) + protected virtual void CustomTestSetup(IUmbracoBuilder builder) { - if (TestOptions.Boot) - { - Services.GetRequiredService().EnsureUmbracoContext(); - } - - app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast } - private static readonly object s_dbLocker = new object(); - private static ITestDatabase s_dbInstance; - private static TestDbMeta s_fixtureDbMeta; - - protected void UseTestDatabase(IServiceProvider serviceProvider) - { - IRuntimeState state = serviceProvider.GetRequiredService(); - TestUmbracoDatabaseFactoryProvider testDatabaseFactoryProvider = serviceProvider.GetRequiredService(); - IUmbracoDatabaseFactory databaseFactory = serviceProvider.GetRequiredService(); - ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); - - // This will create a db, install the schema and ensure the app is configured to run - SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory); - } - - /// - /// Get or create an instance of - /// - /// - /// There must only be ONE instance shared between all tests in a session - /// - private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory) - { - lock (s_dbLocker) - { - if (s_dbInstance != null) - { - return s_dbInstance; - } - - // TODO: pull from IConfiguration - var settings = new TestDatabaseSettings - { - PrepareThreadCount = 4, - EmptyDatabasesCount = 2, - SchemaDatabaseCount = 4 - }; - - s_dbInstance = TestDatabaseFactory.Create(settings, filesPath, loggerFactory, dbFactory); - - return s_dbInstance; - } - } - - /// - /// Creates a LocalDb instance to use for the test - /// - private void SetupTestDatabase( - TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider, - IUmbracoDatabaseFactory databaseFactory, - ILoggerFactory loggerFactory, - IRuntimeState runtimeState, - string workingDirectory) - { - if (TestOptions.Database == UmbracoTestOptions.Database.None) - { - return; - } - - // need to manually register this factory - DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - - string dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - - ITestDatabase db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider); - - switch (TestOptions.Database) - { - case UmbracoTestOptions.Database.NewSchemaPerTest: - - // New DB + Schema - TestDbMeta newSchemaDbMeta = db.AttachSchema(); - - // Add teardown callback - OnTestTearDown(() => db.Detach(newSchemaDbMeta)); - - ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState); - - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewEmptyPerTest: - TestDbMeta newEmptyDbMeta = db.AttachEmpty(); - - // Add teardown callback - OnTestTearDown(() => db.Detach(newEmptyDbMeta)); - - ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState); - - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewSchemaPerFixture: - // Only attach schema once per fixture - // Doing it more than once will block the process since the old db hasn't been detached - // and it would be the same as NewSchemaPerTest even if it didn't block - if (_firstTestInFixture) - { - // New DB + Schema - TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema(); - s_fixtureDbMeta = newSchemaFixtureDbMeta; - - // Add teardown callback - OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta)); - } - - ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); - - break; - case UmbracoTestOptions.Database.NewEmptyPerFixture: - // Only attach schema once per fixture - // Doing it more than once will block the process since the old db hasn't been detached - // and it would be the same as NewSchemaPerTest even if it didn't block - if (_firstTestInFixture) - { - // New DB + Schema - TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty(); - s_fixtureDbMeta = newEmptyFixtureDbMeta; - - // Add teardown callback - OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta)); - } - - ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); - - break; - default: - throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null); - } - } - - private void ConfigureTestDatabaseFactory(TestDbMeta meta, IUmbracoDatabaseFactory factory, IRuntimeState state) - { - ILogger log = Services.GetRequiredService>(); - log.LogInformation($"ConfigureTestDatabaseFactory - Using test database: [{meta.Name}] - IsEmpty: [{meta.IsEmpty}]"); - - // It's just been pulled from container and wasn't used to create test database - Assert.IsFalse(factory.Configured); - - factory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer); - state.DetermineRuntimeLevel(); - log.LogInformation($"ConfigureTestDatabaseFactory - Determined RuntimeLevel: [{state.Level}]"); - } - - protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions(); - protected virtual T GetRequiredService() => Services.GetRequiredService(); - public Dictionary InMemoryConfiguration { get; } = new Dictionary(); - - public IConfiguration Configuration { get; protected set; } - - public TestHelper TestHelper { get; } = new TestHelper(); - - protected virtual void CustomTestSetup(IUmbracoBuilder builder) { } - - /// - /// Gets or sets the DI container. - /// - protected IServiceProvider Services { get; set; } - /// /// Gets the /// @@ -451,11 +174,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing protected IMapperCollection Mappers => Services.GetRequiredService(); - protected UserBuilder UserBuilderInstance { get; } = new UserBuilder(); - protected UserGroupBuilder UserGroupBuilderInstance { get; } = new UserGroupBuilder(); + protected UserBuilder UserBuilderInstance { get; } = new (); - private static bool s_firstTestInSession = true; - private bool _firstTestInFixture = true; - private static int s_testCount = 1; + protected UserGroupBuilder UserGroupBuilderInstance { get; } = new (); } } diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs new file mode 100644 index 0000000000..439d169c61 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; +using Serilog; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Implementations; + +namespace Umbraco.Cms.Tests.Integration.Testing; + +/// +/// Base class for all UmbracoIntegrationTests +/// +[SingleThreaded] +[NonParallelizable] +public abstract class UmbracoIntegrationTestBase +{ + private static readonly object s_dbLocker = new (); + private static ITestDatabase s_dbInstance; + private static TestDbMeta s_fixtureDbMeta; + private static int s_testCount = 1; + + private bool _firstTestInFixture = true; + private readonly Queue _testTeardown = new (); + private readonly List _fixtureTeardown = new (); + + protected Dictionary InMemoryConfiguration { get; } = new (); + + protected IConfiguration Configuration { get; set; } + + protected UmbracoTestAttribute TestOptions => + TestOptionAttributeBase.GetTestOptions(); + + protected TestHelper TestHelper { get; } = new (); + + private void AddOnTestTearDown(Action tearDown) => _testTeardown.Enqueue(tearDown); + + private void AddOnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); + + [SetUp] + public void SetUp_Logging() => + TestContext.Progress.Write($"Start test {s_testCount++}: {TestContext.CurrentContext.Test.Name}"); + + [TearDown] + public void TearDown_Logging() => + TestContext.Progress.Write($" {TestContext.CurrentContext.Result.Outcome.Status}"); + + [OneTimeTearDown] + public void FixtureTearDown() + { + foreach (Action a in _fixtureTeardown) + { + a(); + } + } + + [TearDown] + public void TearDown() + { + _firstTestInFixture = false; + + while (_testTeardown.TryDequeue(out Action a)) + { + a(); + } + } + + protected ILoggerFactory CreateLoggerFactory() + { + try + { + switch (TestOptions.Logger) + { + case UmbracoTestOptions.Logger.Mock: + return NullLoggerFactory.Instance; + case UmbracoTestOptions.Logger.Serilog: + return LoggerFactory.Create(builder => + { + var path = Path.Combine(TestHelper.WorkingDirectory, "logs", "umbraco_integration_tests_.txt"); + + Log.Logger = new LoggerConfiguration() + .WriteTo.File(path, rollingInterval: RollingInterval.Day) + .MinimumLevel.Debug() + .CreateLogger(); + + builder.AddSerilog(Log.Logger); + }); + case UmbracoTestOptions.Logger.Console: + return LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); + } + } + catch + { + // ignored + } + + return NullLoggerFactory.Instance; + } + + protected virtual void ConfigureTestSpecificServices(IServiceCollection services) + { + } + + protected void UseTestDatabase(IApplicationBuilder app) + => UseTestDatabase(app.ApplicationServices); + + protected void UseTestDatabase(IServiceProvider serviceProvider) + { + IRuntimeState state = serviceProvider.GetRequiredService(); + TestUmbracoDatabaseFactoryProvider testDatabaseFactoryProvider = serviceProvider.GetRequiredService(); + IUmbracoDatabaseFactory databaseFactory = serviceProvider.GetRequiredService(); + ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); + + // This will create a db, install the schema and ensure the app is configured to run + SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory); + } + + private void ConfigureTestDatabaseFactory(TestDbMeta meta, IUmbracoDatabaseFactory factory, IRuntimeState state) + { + // It's just been pulled from container and wasn't used to create test database + Assert.IsFalse(factory.Configured); + + factory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer); + state.DetermineRuntimeLevel(); + } + + private void SetupTestDatabase( + TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider, + IUmbracoDatabaseFactory databaseFactory, + ILoggerFactory loggerFactory, + IRuntimeState runtimeState, + string workingDirectory) + { + if (TestOptions.Database == UmbracoTestOptions.Database.None) + { + return; + } + + // need to manually register this factory + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); + + var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); + + ITestDatabase db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider); + + switch (TestOptions.Database) + { + case UmbracoTestOptions.Database.NewSchemaPerTest: + + // New DB + Schema + TestDbMeta newSchemaDbMeta = db.AttachSchema(); + + // Add teardown callback + AddOnTestTearDown(() => db.Detach(newSchemaDbMeta)); + + ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState); + + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewEmptyPerTest: + TestDbMeta newEmptyDbMeta = db.AttachEmpty(); + + // Add teardown callback + AddOnTestTearDown(() => db.Detach(newEmptyDbMeta)); + + ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState); + + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewSchemaPerFixture: + // Only attach schema once per fixture + // Doing it more than once will block the process since the old db hasn't been detached + // and it would be the same as NewSchemaPerTest even if it didn't block + if (_firstTestInFixture) + { + // New DB + Schema + TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema(); + s_fixtureDbMeta = newSchemaFixtureDbMeta; + + // Add teardown callback + AddOnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta)); + } + + ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); + + break; + case UmbracoTestOptions.Database.NewEmptyPerFixture: + // Only attach schema once per fixture + // Doing it more than once will block the process since the old db hasn't been detached + // and it would be the same as NewSchemaPerTest even if it didn't block + if (_firstTestInFixture) + { + // New DB + Schema + TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty(); + s_fixtureDbMeta = newEmptyFixtureDbMeta; + + // Add teardown callback + AddOnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta)); + } + + ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null); + } + } + + private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory) + { + lock (s_dbLocker) + { + if (s_dbInstance != null) + { + return s_dbInstance; + } + + // TODO: pull from IConfiguration + var settings = new TestDatabaseSettings + { + PrepareThreadCount = 4, + EmptyDatabasesCount = 2, + SchemaDatabaseCount = 4 + }; + + s_dbInstance = TestDatabaseFactory.Create(settings, filesPath, loggerFactory, dbFactory); + + return s_dbInstance; + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs index 52fade8dc2..d0d90c5726 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs @@ -24,14 +24,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RuntimeStateTests : UmbracoIntegrationTest { - private protected IRuntimeState RuntimeState { get; private set; } - - public override void Configure(IApplicationBuilder app) - { - base.Configure(app); - - RuntimeState = Services.GetRequiredService(); - } + private IRuntimeState RuntimeState => Services.GetRequiredService(); protected override void CustomTestSetup(IUmbracoBuilder builder) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index bbb9ea8766..1fcb14f0f5 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Castle.Core.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -16,6 +17,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping { @@ -28,16 +30,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping [SetUp] public void SetUp() => Assert.IsNull(ScopeProvider.AmbientScope); // gone - protected override AppCaches GetAppCaches() + + protected override void ConfigureTestSpecificServices(IServiceCollection services) { // Need to have a mockable request cache for tests var appCaches = new AppCaches( NoAppCache.Instance, Mock.Of(x => x.IsAvailable == false), new IsolatedCaches(_ => NoAppCache.Instance)); - return appCaches; + + services.AddUnique(appCaches); } + [Test] public void GivenUncompletedScopeOnChildThread_WhenTheParentCompletes_TheTransactionIsRolledBack() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 0c0508e523..f9a844817e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -31,15 +31,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping private ILocalizationService LocalizationService => GetRequiredService(); - protected override AppCaches GetAppCaches() + protected override void ConfigureTestSpecificServices(IServiceCollection services) { // this is what's created core web runtime - var result = new AppCaches( + var appCaches = new AppCaches( new DeepCloneAppCache(new ObjectCacheAppCache()), NoAppCache.Instance, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - return result; + services.AddUnique(appCaches); } protected override void CustomTestSetup(IUmbracoBuilder builder) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs index 4e4ce29e9a..ecbd8d52aa 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/EntityControllerTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -21,6 +22,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers [TestFixture] public class EntityControllerTests : UmbracoTestServerTestBase { + private IScopeProvider ScopeProvider => GetRequiredService(); + [Test] public async Task GetUrlsByIds_MediaWithIntegerIds_ReturnsValidMap() { From fcd4ad17bb5b794b81db26312f322b7546be0f83 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Sat, 12 Feb 2022 11:40:39 +0000 Subject: [PATCH 3/4] Make webhost setup less confusing. --- .../UmbracoTestServerTestBase.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 6d7835b889..632bbe87d3 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -142,32 +142,26 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest Configuration = configBuilder.Build(); }) - /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup - * found in Program.cs and avoids nasty surprises. - * - * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered - * at the point in time that the service collection is snapshotted. - */ .ConfigureWebHost(builder => { - // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); + builder.ConfigureServices((context, services) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + + ConfigureServices(services); + ConfigureTestSpecificServices(services); + + if (!TestOptions.Boot) + { + // If boot is false, we don't want the CoreRuntime hosted service to start + // So we replace it with a Mock + services.AddUnique(Mock.Of()); + } + }); // call startup builder.Configure(Configure); }) - .ConfigureServices((_, services) => - { - ConfigureServices(services); - ConfigureTestSpecificServices(services); - - if (!TestOptions.Boot) - { - // If boot is false, we don't want the CoreRuntime hosted service to start - // So we replace it with a Mock - services.AddUnique(Mock.Of()); - } - }) .UseDefaultServiceProvider(cfg => { // These default to true *if* WebHostEnvironment.EnvironmentName == Development From a1e562cab613ec56ed1cfa2e7c251ee2f5286018 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Sat, 12 Feb 2022 11:57:28 +0000 Subject: [PATCH 4/4] Switch ConfigureServices back to protected. Downstream users may wish to subclass and add their application specific services. --- .../TestServerTest/UmbracoTestServerTestBase.cs | 13 ++++++++++--- .../Testing/UmbracoIntegrationTest.cs | 17 +++++++++++++++-- .../Testing/UmbracoIntegrationTestBase.cs | 4 ---- .../Umbraco.Core/Events/EventAggregatorTests.cs | 2 +- .../UmbracoExamine/ExamineBaseTest.cs | 2 +- .../Scoping/ScopeTests.cs | 2 +- .../Scoping/ScopedRepositoryTests.cs | 2 +- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 632bbe87d3..8c7eabadde 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -149,7 +149,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); ConfigureServices(services); - ConfigureTestSpecificServices(services); + ConfigureTestServices(services); if (!TestOptions.Boot) { @@ -179,7 +179,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected virtual T GetRequiredService() => Factory.Services.GetRequiredService(); - private void ConfigureServices(IServiceCollection services) + protected void ConfigureServices(IServiceCollection services) { services.AddUnique(CreateLoggerFactory()); services.AddTransient(); @@ -228,7 +228,14 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .Build(); } - private void Configure(IApplicationBuilder app) + /// + /// Hook for registering test doubles. + /// + protected virtual void ConfigureTestServices(IServiceCollection services) + { + } + + protected void Configure(IApplicationBuilder app) { UseTestDatabase(app); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 1e9ae97474..dd9fd3fe4e 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -80,7 +80,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing .ConfigureServices((_, services) => { ConfigureServices(services); - ConfigureTestSpecificServices(services); + ConfigureTestServices(services); if (!TestOptions.Boot) { @@ -93,7 +93,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing return hostBuilder; } - private void ConfigureServices(IServiceCollection services) + protected void ConfigureServices(IServiceCollection services) { services.AddUnique(CreateLoggerFactory()); services.AddSingleton(TestHelper.DbProviderFactoryCreator); @@ -143,10 +143,23 @@ namespace Umbraco.Cms.Tests.Integration.Testing builder.Build(); } + /// + /// Hook for altering UmbracoBuilder setup + /// + /// + /// Can also be used for registering test doubles. + /// protected virtual void CustomTestSetup(IUmbracoBuilder builder) { } + /// + /// Hook for registering test doubles. + /// + protected virtual void ConfigureTestServices(IServiceCollection services) + { + } + protected virtual T GetRequiredService() => Services.GetRequiredService(); /// diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs index 439d169c61..b77ec1806f 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs @@ -107,10 +107,6 @@ public abstract class UmbracoIntegrationTestBase return NullLoggerFactory.Instance; } - protected virtual void ConfigureTestSpecificServices(IServiceCollection services) - { - } - protected void UseTestDatabase(IApplicationBuilder app) => UseTestDatabase(app.ApplicationServices); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs index 55429af147..4f7db59791 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events [TestFixture] public class EventAggregatorTests : UmbracoTestServerTestBase { - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { services.AddScoped(); services.AddTransient, EventAggregatorTestNotificationHandler>(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 7769f99d83..95b708f167 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) => services.AddSingleton(); /// diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index 1fcb14f0f5..c4f2db58fe 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -31,7 +31,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping public void SetUp() => Assert.IsNull(ScopeProvider.AmbientScope); // gone - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { // Need to have a mockable request cache for tests var appCaches = new AppCaches( diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index f9a844817e..c228e07178 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -31,7 +31,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping private ILocalizationService LocalizationService => GetRequiredService(); - protected override void ConfigureTestSpecificServices(IServiceCollection services) + protected override void ConfigureTestServices(IServiceCollection services) { // this is what's created core web runtime var appCaches = new AppCaches(