diff --git a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs b/src/Umbraco.Tests.Common/Testing/TestOptionAttributeBase.cs similarity index 88% rename from src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs rename to src/Umbraco.Tests.Common/Testing/TestOptionAttributeBase.cs index 2fb4660481..bbfa68778f 100644 --- a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs +++ b/src/Umbraco.Tests.Common/Testing/TestOptionAttributeBase.cs @@ -31,10 +31,14 @@ namespace Umbraco.Tests.Testing var test = TestContext.CurrentContext.Test; var typeName = test.ClassName; var methodName = test.MethodName; + // this will only get types from whatever is already loaded in the app domain var type = Type.GetType(typeName, false); if (type == null) { - type = ScanAssemblies + // automatically add the executing and calling assemblies to the list to scan for this type + var scanAssemblies = ScanAssemblies.Union(new[] {Assembly.GetExecutingAssembly(), Assembly.GetCallingAssembly()}).ToList(); + + type = scanAssemblies .Select(assembly => assembly.GetType(typeName, false)) .FirstOrDefault(x => x != null); if (type == null) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs b/src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs similarity index 100% rename from src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs rename to src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs diff --git a/src/Umbraco.Tests.Common/Testing/UmbracoTestOptions.cs b/src/Umbraco.Tests.Common/Testing/UmbracoTestOptions.cs new file mode 100644 index 0000000000..eef6e45d7e --- /dev/null +++ b/src/Umbraco.Tests.Common/Testing/UmbracoTestOptions.cs @@ -0,0 +1,61 @@ +namespace Umbraco.Tests.Testing +{ + public static class UmbracoTestOptions + { + public enum Logger + { + /// + /// pure mocks + /// + Mock, + /// + /// Serilog for tests + /// + Serilog, + /// + /// console logger + /// + Console + } + + public enum Database + { + /// + /// no database + /// + None, + /// + /// new empty database file for the entire fixture + /// + NewEmptyPerFixture, + /// + /// new empty database file per test + /// + NewEmptyPerTest, + /// + /// new database file with schema for the entire fixture + /// + NewSchemaPerFixture, + /// + /// new database file with schema per test + /// + NewSchemaPerTest + } + + public enum TypeLoader + { + /// + /// the default, global type loader for tests + /// + Default, + /// + /// create one type loader for the feature + /// + PerFixture, + /// + /// create one type loader for each test + /// + PerTest + } + } +} diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs index 945eeda2f0..34a32a38a8 100644 --- a/src/Umbraco.Tests.Integration/ContainerTests.cs +++ b/src/Umbraco.Tests.Integration/ContainerTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; using Umbraco.Tests.Common; using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; namespace Umbraco.Tests.Integration { @@ -82,7 +83,7 @@ namespace Umbraco.Tests.Integration // it means the container won't be disposed, and maybe other services? not sure. // In cases where we use it can we use IConfigureOptions? https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ - var umbracoContainer = RuntimeTests.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); IHostApplicationLifetime lifetime1 = null; diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..553aa4fe85 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,117 @@ +using System; +using System.Data.Common; +using System.Data.SqlClient; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Configuration.Models; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Integration.Extensions +{ + public static class ApplicationBuilderExtensions + { + /// + /// Creates a LocalDb instance to use for the test + /// + /// + /// + /// + /// + public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app, + string dbFilePath, + UmbracoIntegrationTest integrationTest) + { + // get the currently set db options + var testOptions = TestOptionAttributeBase.GetTestOptions(); + + if (testOptions.Database == UmbracoTestOptions.Database.None) + return app; + + // need to manually register this factory + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); + + if (!Directory.Exists(dbFilePath)) + Directory.CreateDirectory(dbFilePath); + + var db = UmbracoIntegrationTest.GetOrCreate(dbFilePath, + app.ApplicationServices.GetRequiredService(), + app.ApplicationServices.GetRequiredService(), + app.ApplicationServices.GetRequiredService()); + + switch (testOptions.Database) + { + case UmbracoTestOptions.Database.NewSchemaPerTest: + + // Add teardown callback + integrationTest.OnTestTearDown(() => db.Detach()); + + // New DB + Schema + db.AttachSchema(); + + // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings + var dbFactory = app.ApplicationServices.GetRequiredService(); + if (!dbFactory.Configured) + { + dbFactory.Configure(db.ConnectionString, Umbraco.Core.Constants.DatabaseProviders.SqlServer); + } + + // In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that + // the runtime state is configured correctly so we'll force update the configuration flag and re-run the + // runtime state checker. + // TODO: This wouldn't be required if we don't store the Umbraco version in config + + // right now we are an an 'Install' state + var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService(); + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + + // dynamically change the config status + var umbVersion = app.ApplicationServices.GetRequiredService(); + var config = app.ApplicationServices.GetRequiredService(); + config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); + + // re-run the runtime level check + var profilingLogger = app.ApplicationServices.GetRequiredService(); + runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); + + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewEmptyPerTest: + + // Add teardown callback + integrationTest.OnTestTearDown(() => db.Detach()); + + db.AttachEmpty(); + + break; + case UmbracoTestOptions.Database.NewSchemaPerFixture: + + // Add teardown callback + integrationTest.OnFixtureTearDown(() => db.Detach()); + + break; + case UmbracoTestOptions.Database.NewEmptyPerFixture: + + // Add teardown callback + integrationTest.OnFixtureTearDown(() => db.Detach()); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null); + } + + return app; + } + + } + + +} diff --git a/src/Umbraco.Tests.Integration/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..9dde20036b --- /dev/null +++ b/src/Umbraco.Tests.Integration/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Tests.Integration.Implementations; + +namespace Umbraco.Tests.Integration.Extensions +{ + public static class ServiceCollectionExtensions + { + /// + /// These services need to be manually added because they do not get added by the generic host + /// + /// + /// + /// + public static void AddRequiredNetCoreServices(this IServiceCollection services, TestHelper testHelper, IWebHostEnvironment webHostEnvironment) + { + services.AddSingleton(x => testHelper.GetHttpContextAccessor()); + // the generic host does add IHostEnvironment but not this one because we are not actually in a web context + services.AddSingleton(x => webHostEnvironment); + } + } +} diff --git a/src/Umbraco.Tests.Integration/Implementations/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Implementations/ApplicationBuilderExtensions.cs deleted file mode 100644 index abb22c0bd7..0000000000 --- a/src/Umbraco.Tests.Integration/Implementations/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.Data.SqlClient; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using NUnit.Framework; -using Umbraco.Configuration.Models; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Tests.Integration.Testing; - -namespace Umbraco.Tests.Integration.Implementations -{ - public static class ApplicationBuilderExtensions - { - /// - /// Creates a LocalDb instance to use for the test - /// - /// - /// - /// - /// Default is true - meaning a brand new database is created for this test. If this is false it will try to - /// re-use an existing database that was already created as part of this test fixture. - /// // TODO Implement the 'false' behavior - /// - /// - /// Default is true - meaning a database schema will be created for this test if it's a new database. If this is false - /// it will just create an empty database. - /// - /// - public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app, - string dbFilePath, - bool createNewDb = true, - bool initializeSchema = true) - { - // need to manually register this factory - DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - - if (!Directory.Exists(dbFilePath)) - Directory.CreateDirectory(dbFilePath); - - var db = LocalDbTestDatabase.Get(dbFilePath, - app.ApplicationServices.GetRequiredService(), - app.ApplicationServices.GetRequiredService(), - app.ApplicationServices.GetRequiredService()); - - if (initializeSchema) - { - // New DB + Schema - db.AttachSchema(); - - // In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that - // the runtime state is configured correctly so we'll force update the configuration flag and re-run the - // runtime state checker. - // TODO: This wouldn't be required if we don't store the Umbraco version in config - - // right now we are an an 'Install' state - var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService(); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - - // dynamically change the config status - var umbVersion = app.ApplicationServices.GetRequiredService(); - var config = app.ApplicationServices.GetRequiredService(); - config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); - - // re-run the runtime level check - var dbFactory = app.ApplicationServices.GetRequiredService(); - var profilingLogger = app.ApplicationServices.GetRequiredService(); - runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); - - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - - } - else - { - db.AttachEmpty(); - } - - return app; - } - - } - - -} diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests.Integration/Persistence/Repositories/AuditRepositoryTest.cs similarity index 72% rename from src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs rename to src/Umbraco.Tests.Integration/Persistence/Repositories/AuditRepositoryTest.cs index 49e48e0a2f..e92653fb98 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Persistence/Repositories/AuditRepositoryTest.cs @@ -1,29 +1,29 @@ using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; -using Umbraco.Core; -namespace Umbraco.Tests.Persistence.Repositories +namespace Umbraco.Tests.Integration.Persistence.Repositories { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] - public class AuditRepositoryTest : TestWithDatabaseBase + public class AuditRepositoryTest : UmbracoIntegrationTest { [Test] public void Can_Add_Audit_Entry() { - var sp = TestObjects.GetScopeProvider(Logger); - using (var scope = sp.CreateScope()) + var sp = ScopeProvider; + using (var scope = ScopeProvider.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); - repo.Save(new AuditItem(-1, AuditType.System, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "This is a System audit trail")); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); + repo.Save(new AuditItem(-1, AuditType.System, -1, UmbracoObjectTypes.Document.GetName(), "This is a System audit trail")); var dtos = scope.Database.Fetch("WHERE id > -1"); @@ -35,15 +35,15 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_Paged_Items() { - var sp = TestObjects.GetScopeProvider(Logger); + var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); - repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); + repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published")); } scope.Complete(); @@ -51,7 +51,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 10, out var total, Direction.Descending, null, null); @@ -63,15 +63,15 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_Paged_Items_By_User_Id_With_Query_And_Filter() { - var sp = TestObjects.GetScopeProvider(Logger); + var sp = ScopeProvider; using (var scope = sp.CreateScope()) { var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); - repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); + repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published")); } scope.Complete(); @@ -106,15 +106,15 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_Paged_Items_With_AuditType_Filter() { - var sp = TestObjects.GetScopeProvider(Logger); + var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created")); - repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published")); + repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published")); } scope.Complete(); @@ -122,10 +122,10 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 9, out var total, Direction.Descending, - new[] {AuditType.Publish}, null) + new[] { AuditType.Publish }, null) .ToArray(); Assert.AreEqual(9, page.Length); @@ -137,15 +137,15 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_Paged_Items_With_Custom_Filter() { - var sp = TestObjects.GetScopeProvider(Logger); + var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content created")); - repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content published")); + repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), "Content created")); + repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), "Content published")); } scope.Complete(); @@ -153,7 +153,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 8, out var total, Direction.Descending, null, sp.SqlContext.Query().Where(item => item.Comment == "Content created")) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 814687eb7f..210f767de1 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -1,30 +1,21 @@ -using LightInject; -using LightInject.Microsoft.DependencyInjection; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; -using Umbraco.Configuration.Models; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.LightInject; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; using Umbraco.Tests.Common; +using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.Integration.Testing; using Umbraco.Web.BackOffice.AspNetCore; -using static Umbraco.Core.Migrations.Install.DatabaseBuilder; namespace Umbraco.Tests.Integration { @@ -39,10 +30,11 @@ namespace Umbraco.Tests.Integration MyComposer.Reset(); } - [OneTimeTearDown] - public void FixtureTearDown() + [SetUp] + public void Setup() { - + MyComponent.Reset(); + MyComposer.Reset(); } /// @@ -95,7 +87,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task AddUmbracoCore() { - var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() @@ -103,7 +95,7 @@ namespace Umbraco.Tests.Integration .ConfigureServices((hostContext, services) => { var webHostEnvironment = testHelper.GetWebHostEnvironment(); - AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); + services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); @@ -134,7 +126,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task UseUmbracoCore() { - var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() @@ -142,7 +134,7 @@ namespace Umbraco.Tests.Integration .ConfigureServices((hostContext, services) => { var webHostEnvironment = testHelper.GetWebHostEnvironment(); - AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); + services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); @@ -169,56 +161,6 @@ namespace Umbraco.Tests.Integration Assert.IsTrue(MyComponent.IsTerminated); } - [Test] - public async Task Install_Database() - { - var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); - var testHelper = new TestHelper(); - - var hostBuilder = new HostBuilder() - .UseUmbraco(serviceProviderFactory) - .ConfigureServices((hostContext, services) => - { - var webHostEnvironment = testHelper.GetWebHostEnvironment(); - AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); - - // Add it! - services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); - }); - - var host = await hostBuilder.StartAsync(); - var app = new ApplicationBuilder(host.Services); - - // This will create a db, install the schema and ensure the app is configured to run - app.UseTestLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb")); - - app.UseUmbracoCore(); - - var runtimeState = app.ApplicationServices.GetRequiredService(); - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - } - - internal static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) - { - var container = UmbracoServiceProviderFactory.CreateServiceContainer(); - serviceProviderFactory = new UmbracoServiceProviderFactory(container); - var umbracoContainer = serviceProviderFactory.GetContainer(); - return umbracoContainer; - } - - /// - /// These services need to be manually added because they do not get added by the generic host - /// - /// - /// - /// - private void AddRequiredNetCoreServices(IServiceCollection services, TestHelper testHelper, IWebHostEnvironment webHostEnvironment) - { - services.AddSingleton(x => testHelper.GetHttpContextAccessor()); - // the generic host does add IHostEnvironment but not this one because we are not actually in a web context - services.AddSingleton(x => webHostEnvironment); - } [RuntimeLevel(MinLevel = RuntimeLevel.Install)] public class MyComposer : IUserComposer diff --git a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs index 63b336701c..da2b83bc39 100644 --- a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs +++ b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs @@ -17,16 +17,8 @@ namespace Umbraco.Tests.Integration.Testing /// /// Manages a pool of LocalDb databases for integration testing /// - internal class LocalDbTestDatabase + public class LocalDbTestDatabase { - public static LocalDbTestDatabase Get(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory) - { - var localDb = new LocalDb(); - if (localDb.IsAvailable == false) - throw new InvalidOperationException("LocalDB is not available."); - return new LocalDbTestDatabase(logger, globalSettings, localDb, filesPath, dbFactory); - } - public const string InstanceName = "UmbracoTests"; public const string DatabaseName = "UmbracoTests"; @@ -43,7 +35,8 @@ namespace Umbraco.Tests.Integration.Testing private static DatabasePool _schemaPool; private DatabasePool _currentPool; - public LocalDbTestDatabase(ILogger logger, IGlobalSettings globalSettings, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory) + //It's internal because `Umbraco.Core.Persistence.LocalDb` is internal + internal LocalDbTestDatabase(ILogger logger, IGlobalSettings globalSettings, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory) { _umbracoVersion = new UmbracoVersion(); _logger = logger; @@ -328,5 +321,6 @@ namespace Umbraco.Tests.Integration.Testing while (_readyQueue.TryTake(out i)) { } } } + } } diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs new file mode 100644 index 0000000000..90a6e7bdf8 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Composing.LightInject; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Scoping; +using Umbraco.Tests.Integration.Extensions; +using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Testing; +using Umbraco.Web.BackOffice.AspNetCore; + +namespace Umbraco.Tests.Integration.Testing +{ + /// + /// Abstract class for integration tests + /// + /// + /// This will use a Host Builder to boot and install Umbraco ready for use + /// + [SingleThreaded] + [NonParallelizable] + public abstract class UmbracoIntegrationTest + { + public static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) + { + var container = UmbracoServiceProviderFactory.CreateServiceContainer(); + serviceProviderFactory = new UmbracoServiceProviderFactory(container); + var umbracoContainer = serviceProviderFactory.GetContainer(); + return umbracoContainer; + } + + /// + /// Get or create an instance of + /// + /// + /// + /// + /// + /// + /// + /// There must only be ONE instance shared between all tests in a session + /// + public static LocalDbTestDatabase GetOrCreate(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory) + { + lock (_dbLocker) + { + if (_dbInstance != null) return _dbInstance; + + var localDb = new LocalDb(); + if (localDb.IsAvailable == false) + throw new InvalidOperationException("LocalDB is not available."); + _dbInstance = new LocalDbTestDatabase(logger, globalSettings, localDb, filesPath, dbFactory); + return _dbInstance; + } + } + + private static readonly object _dbLocker = new object(); + private static LocalDbTestDatabase _dbInstance; + + private readonly List _testTeardown = new List(); + private readonly List _fixtureTeardown = new List(); + + public void OnTestTearDown(Action tearDown) + { + _testTeardown.Add(tearDown); + } + + public void OnFixtureTearDown(Action tearDown) + { + _fixtureTeardown.Add(tearDown); + } + + [OneTimeTearDown] + public void FixtureTearDown() + { + // call all registered callbacks + foreach (var action in _fixtureTeardown) + { + action(); + } + } + + [TearDown] + public void TearDown() + { + // call all registered callbacks + foreach (var action in _testTeardown) + { + action(); + } + } + + [SetUp] + public async Task Setup() + { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + var testHelper = new TestHelper(); + + var hostBuilder = new HostBuilder() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + var webHostEnvironment = testHelper.GetWebHostEnvironment(); + services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); + + // Add it! + services.AddUmbracoConfiguration(hostContext.Configuration); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + }); + + var host = await hostBuilder.StartAsync(); + var app = new ApplicationBuilder(host.Services); + Services = app.ApplicationServices; + + // This will create a db, install the schema and ensure the app is configured to run + app.UseTestLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb"), this); + + app.UseUmbracoCore(); + } + + /// + /// Returns the DI container + /// + protected IServiceProvider Services { get; private set; } + + /// + /// Returns the + /// + protected IScopeProvider ScopeProvider => Services.GetRequiredService(); + + /// + /// Returns the + /// + protected IScopeAccessor ScopeAccessor => Services.GetRequiredService(); + + /// + /// Returns the + /// + protected ILogger Logger => Services.GetRequiredService(); + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index fa037fbb50..4609d8a59c 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -252,8 +252,8 @@ namespace Umbraco.Tests.TestHelpers TestHelper.DbProviderFactoryCreator); } - typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); - fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); + typeFinder ??= new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + fileSystems ??= new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = TestHelper.CoreDebugSettings; var mediaFileSystem = Mock.Of(); var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, coreDebug, mediaFileSystem, logger, typeFinder, NoAppCache.Instance); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs deleted file mode 100644 index da3ffccc55..0000000000 --- a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Umbraco.Tests.Testing -{ - public static class UmbracoTestOptions - { - public enum Logger - { - // pure mocks - Mock, - // Serilog for tests - Serilog, - // console logger - Console - } - - public enum Database - { - // no database - None, - // new empty database file for the entire feature - NewEmptyPerFixture, - // new empty database file per test - NewEmptyPerTest, - // new database file with schema for the entire feature - NewSchemaPerFixture, - // new database file with schema per test - NewSchemaPerTest - } - - public enum TypeLoader - { - // the default, global type loader for tests - Default, - // create one type loader for the feature - PerFixture, - // create one type loader for each test - PerTest - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 06f6a98573..f667b11aa3 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -231,11 +231,8 @@ - - - @@ -267,7 +264,6 @@ -