diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 477ff9c..270cf32 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,14 +36,12 @@ jobs: - name: Build run: dotnet build --no-restore --configuration Release - name: Test + working-directory: Tests/CleanAspCore.Api.Tests run: > - dotnet test + dotnet run --no-build --configuration Release - --verbosity normal - --logger GitHubActions - -- - RunConfiguration.CollectSourceInformation=true + --report-trx - name: dotnet publish working-directory: CleanAspCore.Api run: dotnet publish --no-build -c Release -o ${{env.DOTNET_ROOT}}/myapp diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3045283..de0f100 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -3,6 +3,10 @@ name: .NET +permissions: + checks: write + pull-requests: write + on: push: branches: [ "main" ] @@ -35,12 +39,38 @@ jobs: - name: Check code formatting run: dotnet format --no-restore --verify-no-changes -v diag - name: Build - run: dotnet build --no-restore + run: dotnet build + --no-restore + --configuration Release - name: Test + working-directory: Tests/CleanAspCore.Api.Tests run: > - dotnet test + dotnet run --no-build - --verbosity normal - --logger GitHubActions - -- - RunConfiguration.CollectSourceInformation=true + --configuration Release + --coverage + --coverage-output-format cobertura + --report-trx + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + **/*.trx + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: "**/*.cobertura.xml" + badge: true + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-results.md diff --git a/CleanAspCore.sln b/CleanAspCore.sln index 3991631..170bceb 100644 --- a/CleanAspCore.sln +++ b/CleanAspCore.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt Readme.md = Readme.md .github\workflows\dotnet.yml = .github\workflows\dotnet.yml .github\workflows\deploy.yml = .github\workflows\deploy.yml + Directory.Packages.props = Directory.Packages.props EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanAspCore.Api.Tests", "Tests\CleanAspCore.Api.Tests\CleanAspCore.Api.Tests.csproj", "{4B45D679-E787-4236-BD9A-383364CD0E6F}" diff --git a/Directory.Packages.props b/Directory.Packages.props index e550762..459bafe 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,10 +23,9 @@ - - - - + + + diff --git a/Tests/CleanAspCore.Api.Tests/CleanAspCore.Api.Tests.csproj b/Tests/CleanAspCore.Api.Tests/CleanAspCore.Api.Tests.csproj index a2c905f..0ebbe9a 100644 --- a/Tests/CleanAspCore.Api.Tests/CleanAspCore.Api.Tests.csproj +++ b/Tests/CleanAspCore.Api.Tests/CleanAspCore.Api.Tests.csproj @@ -6,23 +6,11 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + diff --git a/Tests/CleanAspCore.Api.Tests/Data/MigrationTests.cs b/Tests/CleanAspCore.Api.Tests/Data/MigrationTests.cs index 6b8f981..38cab40 100644 --- a/Tests/CleanAspCore.Api.Tests/Data/MigrationTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Data/MigrationTests.cs @@ -1,43 +1,32 @@ -using System.Collections; -using CleanAspCore.Data; +using CleanAspCore.Data; using CleanAspCore.TestUtils.DataBaseSetup; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Testcontainers.MsSql; namespace CleanAspCore.Api.Tests.Data; -[FixtureLifeCycle(LifeCycle.SingleInstance)] -[Parallelizable(ParallelScope.Self)] -internal sealed class MigrationTests +internal sealed class MigrationTests(MsSqlContainer databaseContainer, ILogger logger) { -#pragma warning disable NUnit1032 - private MsSqlContainer _databaseContainer = null!; -#pragma warning restore NUnit1032 - private ILogger _logger = null!; - private AsyncServiceScope _scope; - - [SetUp] - public void BeforeTestCase() + public static IEnumerable> MigrationTestCases() { - _scope = GlobalSetup.Provider.CreateAsyncScope(); - _databaseContainer = _scope.ServiceProvider.GetRequiredService(); - _logger = _scope.ServiceProvider.GetRequiredService>(); - } + using DbContext context = new HrContext(); + var migrations = context.GenerateMigrationScripts(); - [TearDown] - public async Task AfterTestCase() - { - await _scope.DisposeAsync(); + foreach (var migration in migrations) + { + yield return () => migration; + } } - [TestCaseSource(typeof(MigrationTestCases))] + [Test] + [MethodDataSource(nameof(MigrationTestCases))] + [NotInParallel("MigrationsTest")] public async Task MigrationsUpAndDown_NoErrors(MigrationScript migration) { var databaseName = "MigrationsTest"; - await _databaseContainer.CreateDatabase(databaseName); - var migrator = new SqlMigrator(_databaseContainer, _logger, databaseName); + await databaseContainer.CreateDatabase(databaseName); + var migrator = new SqlMigrator(databaseContainer, logger, databaseName); var upResult = await migrator.Up(migration); upResult.ExitCode.Should().Be(0, $"Error during migration up: {upResult.Stderr}"); var downResult = await migrator.Down(migration); @@ -53,19 +42,4 @@ public void ModelShouldNotHavePendingModelChanges() var hasPendingModelChanges = context.Database.HasPendingModelChanges(); hasPendingModelChanges.Should().BeFalse(); } - - [SuppressMessage("CodeQuality", "CA1812:Avoid uninstantiated internal classes")] - private sealed class MigrationTestCases : IEnumerable - { - public IEnumerator GetEnumerator() - { - using DbContext context = new HrContext(); - var migrations = context.GenerateMigrationScripts(); - - foreach (var migration in migrations) - { - yield return new TestCaseData(migration); - } - } - } } diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/AddDepartmentsTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/AddDepartmentsTests.cs index 8cb7b4c..8b0f7b7 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/AddDepartmentsTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/AddDepartmentsTests.cs @@ -2,7 +2,7 @@ namespace CleanAspCore.Api.Tests.Endpoints.Departments; -internal sealed class AddDepartmentsTests : TestBase +internal sealed class AddDepartmentsTests(TestWebApi sut) { [Test] public async Task CreateDepartment_IsAdded() @@ -11,12 +11,12 @@ public async Task CreateDepartment_IsAdded() var department = new CreateDepartmentRequestFaker().Generate(); //Act - var response = await Sut.CreateClientFor().CreateDepartment(department); + var response = await sut.CreateClientFor().CreateDepartment(department); //Assert await response.AssertStatusCode(HttpStatusCode.Created); var createdId = response.GetGuidFromLocationHeader(); - Sut.AssertDatabase(context => + sut.AssertDatabase(context => { context.Departments.Should().BeEquivalentTo(new[] { new { Id = createdId } }); }); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/GetDepartmentByIdTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/GetDepartmentByIdTests.cs index 68105b3..760e220 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/GetDepartmentByIdTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Departments/GetDepartmentByIdTests.cs @@ -1,19 +1,19 @@ namespace CleanAspCore.Api.Tests.Endpoints.Departments; -internal sealed class GetDepartmentByIdTests : TestBase +internal sealed class GetDepartmentByIdTests(TestWebApi sut) { [Test] public async Task GetDepartmentById_ReturnsExpectedDepartment() { //Arrange var department = new DepartmentFaker().Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Departments.Add(department); }); //Act - var response = await Sut.CreateClientFor().GetDepartmentById(department.Id); + var response = await sut.CreateClientFor().GetDepartmentById(department.Id); //Assert diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/CreateEmployeeTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/CreateEmployeeTests.cs index d6a396b..ab92d84 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/CreateEmployeeTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/CreateEmployeeTests.cs @@ -3,50 +3,51 @@ namespace CleanAspCore.Api.Tests.Endpoints.Employees; -internal sealed class CreateEmployeeTests : TestBase +internal sealed class CreateEmployeeTests(TestWebApi sut) { [Test] public async Task CreateEmployee_IsAdded() { //Arrange var createEmployeeRequest = new CreateEmployeeRequestFaker().Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Departments.Add(new DepartmentFaker().RuleFor(x => x.Id, createEmployeeRequest.DepartmentId).Generate()); context.Jobs.Add(new JobFaker().RuleFor(x => x.Id, createEmployeeRequest.JobId).Generate()); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest); //Assert await response.AssertStatusCode(HttpStatusCode.Created); var createdId = response.GetGuidFromLocationHeader(); - Sut.AssertDatabase(context => + sut.AssertDatabase(context => { context.Employees.Should().BeEquivalentTo(new[] { new { Id = createdId } }); }); } - private static readonly TestScenario<(FakerConfigurator, string[])>[] _validationCases = - [ - new((string)"FirstName is null", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.FirstName, (string?)null), ["FirstName"])), - new((string)"LastName is null", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.LastName, (string?)null), ["LastName"])), - new((string)"Gender is null", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.Gender, (string?)null), ["Gender"])), - new((string)"Email is null", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.Email, (string?)null), ["Email"])), - new((string)"Invalid email", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.Email, "this is not a valid email address"), ["Email"])), - new((string)"Job does not exist", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.JobId, Guid.NewGuid()), ["JobId"])), - new((string)"Department does not exist", - ((FakerConfigurator, string[]))(x => x.RuleFor(y => y.DepartmentId, Guid.NewGuid()), ["DepartmentId"])), - ]; + public static IEnumerable, string[])>>> ValidationTestCases() + { + yield return () => new((string)"FirstName is null", + (x => x.RuleFor(y => y.FirstName, (string?)null), ["FirstName"])); + yield return () => new((string)"LastName is null", + (x => x.RuleFor(y => y.LastName, (string?)null), ["LastName"])); + yield return () => new((string)"Gender is null", + (x => x.RuleFor(y => y.Gender, (string?)null), ["Gender"])); + yield return () => new((string)"Email is null", + (x => x.RuleFor(y => y.Email, (string?)null), ["Email"])); + yield return () => new((string)"Invalid email", + (x => x.RuleFor(y => y.Email, "this is not a valid email address"), ["Email"])); + yield return () => new((string)"Job does not exist", + (x => x.RuleFor(y => y.JobId, Guid.NewGuid()), ["JobId"])); + yield return () => new((string)"Department does not exist", + (x => x.RuleFor(y => y.DepartmentId, Guid.NewGuid()), ["DepartmentId"])); + } - [TestCaseSource(nameof(_validationCases))] + [Test] + [MethodDataSource(nameof(ValidationTestCases))] public async Task CreateEmployee_InvalidRequest_ReturnsBadRequest(TestScenario<(FakerConfigurator configurator, string[] expectedErrors)> scenario) { //Arrange @@ -57,18 +58,18 @@ public async Task CreateEmployee_InvalidRequest_ReturnsBadRequest(TestScenario<( .RuleFor(x => x.DepartmentId, departmentId) .RuleFor(x => x.JobId, jobId)).Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Departments.Add(new DepartmentFaker().RuleFor(x => x.Id, departmentId).Generate()); context.Jobs.Add(new JobFaker().RuleFor(x => x.Id, jobId).Generate()); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest); //Assert await response.AssertBadRequest(scenario.Input.expectedErrors); - Sut.AssertDatabase(context => + sut.AssertDatabase(context => { context.Employees.Should().BeEmpty(); }); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/DeleteEmployeeByIdTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/DeleteEmployeeByIdTests.cs index 9f98a7a..4559060 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/DeleteEmployeeByIdTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/DeleteEmployeeByIdTests.cs @@ -1,23 +1,23 @@ namespace CleanAspCore.Api.Tests.Endpoints.Employees; -internal sealed class DeleteEmployeeByIdTests : TestBase +internal sealed class DeleteEmployeeByIdTests(TestWebApi sut) { [Test] public async Task DeleteEmployeeById_IsDeleted() { //Arrange var employee = new EmployeeFaker().Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Employees.Add(employee); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).DeleteEmployeeById(employee.Id); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).DeleteEmployeeById(employee.Id); //Assert await response.AssertStatusCode(HttpStatusCode.NoContent); - Sut.AssertDatabase(context => { context.Employees.Should().BeEmpty(); }); + sut.AssertDatabase(context => { context.Employees.Should().BeEmpty(); }); } [Test] @@ -27,7 +27,7 @@ public async Task DeleteEmployeeById_DoesNotExist_ReturnsNotFound() var id = Guid.NewGuid(); //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).DeleteEmployeeById(id); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).DeleteEmployeeById(id); //Assert await response.AssertStatusCode(HttpStatusCode.NotFound); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeeByIdTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeeByIdTests.cs index f92578d..6997d99 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeeByIdTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeeByIdTests.cs @@ -1,19 +1,19 @@ namespace CleanAspCore.Api.Tests.Endpoints.Employees; -internal sealed class GetEmployeeByIdTests : TestBase +internal sealed class GetEmployeeByIdTests(TestWebApi sut) { [Test] public async Task GetEmployeeById_ReturnsExpectedEmployee() { //Arrange var employee = new EmployeeFaker().Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Employees.Add(employee); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployeeById(employee.Id); + var response = await sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployeeById(employee.Id); //Assert await response.AssertStatusCode(HttpStatusCode.OK); @@ -27,7 +27,7 @@ public async Task GetEmployeeById_DoesNotExist_ReturnsNotFound() var employee = new EmployeeFaker().Generate(); //Act - var response = await Sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployeeById(employee.Id); + var response = await sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployeeById(employee.Id); //Assert await response.AssertStatusCode(HttpStatusCode.NotFound); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeesTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeesTests.cs index 00f3ce2..46affbc 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeesTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/GetEmployeesTests.cs @@ -1,12 +1,12 @@ namespace CleanAspCore.Api.Tests.Endpoints.Employees; -internal sealed class GetEmployees : TestBase +internal sealed class GetEmployees(TestWebApi sut) { [Test] public async Task? GetEmployees_NoEmployees_ReturnsEmptyPage() { //Act - var response = await Sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(1, 10); + var response = await sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(1, 10); //Assert await response.AssertStatusCode(HttpStatusCode.OK); @@ -29,13 +29,13 @@ public async Task GetEmployees_FirstPage_ReturnsExpectedEmployees() .RuleFor(x => x.Department, department) .RuleFor(x => x.Job, job) .Generate(15); - Sut.SeedData(context => + sut.SeedData(context => { context.Employees.AddRange(employees); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(1, 10); + var response = await sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(1, 10); //Assert await response.AssertStatusCode(HttpStatusCode.OK); @@ -62,13 +62,13 @@ public async Task GetEmployees_SecondPage_ReturnsExpectedEmployees() .RuleFor(x => x.Department, department) .RuleFor(x => x.Job, job) .Generate(15); - Sut.SeedData(context => + sut.SeedData(context => { context.Employees.AddRange(employees); }); //Act - var response = await Sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(2, 10); + var response = await sut.CreateClientFor(ClaimConstants.ReadRole).GetEmployees(2, 10); //Assert await response.AssertStatusCode(HttpStatusCode.OK); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/UpdateEmployeeByIdTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/UpdateEmployeeByIdTests.cs index 813711c..a7ce98b 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/UpdateEmployeeByIdTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Employees/UpdateEmployeeByIdTests.cs @@ -2,23 +2,23 @@ namespace CleanAspCore.Api.Tests.Endpoints.Employees; -internal sealed class UpdateEmployeeByIdTests : TestBase +internal sealed class UpdateEmployeeByIdTests(TestWebApi sut) { [Test] public async Task UpdateEmployeeById_IsUpdated() { //Arrange var employee = new EmployeeFaker().Generate(); - Sut.SeedData(context => { context.Employees.Add(employee); }); + sut.SeedData(context => { context.Employees.Add(employee); }); UpdateEmployeeRequest updateEmployeeRequest = new() { FirstName = "Updated" }; //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest); //Assert await response.AssertStatusCode(HttpStatusCode.NoContent); - Sut.AssertDatabase(context => + sut.AssertDatabase(context => { context.Employees.Should().BeEquivalentTo(new[] { @@ -40,7 +40,7 @@ public async Task UpdateEmployeeById_DoesNotExist_ReturnsNotFound() UpdateEmployeeRequest updateEmployeeRequest = new() { FirstName = "Updated" }; //Act - var response = await Sut.CreateClientFor(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest); + var response = await sut.CreateClientFor(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest); //Assert await response.AssertStatusCode(HttpStatusCode.NotFound); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/CreateJobTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/CreateJobTests.cs index 39a7c0c..a99630b 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/CreateJobTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/CreateJobTests.cs @@ -2,7 +2,7 @@ namespace CleanAspCore.Api.Tests.Endpoints.Jobs; -internal sealed class CreateJobTests : TestBase +internal sealed class CreateJobTests(TestWebApi sut) { [Test] public async Task CreateJob_IsAdded() @@ -11,12 +11,12 @@ public async Task CreateJob_IsAdded() var createJobRequest = new CreateJobRequestFaker().Generate(); //Act - var response = await Sut.CreateClientFor().CreateJob(createJobRequest); + var response = await sut.CreateClientFor().CreateJob(createJobRequest); //Assert await response.AssertStatusCode(HttpStatusCode.Created); var createdId = response.GetGuidFromLocationHeader(); - Sut.AssertDatabase(context => + sut.AssertDatabase(context => { context.Jobs.Should().BeEquivalentTo(new[] { new { Id = createdId } }); }); diff --git a/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/GetJobByIdTests.cs b/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/GetJobByIdTests.cs index 86f8f2a..2246ad9 100644 --- a/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/GetJobByIdTests.cs +++ b/Tests/CleanAspCore.Api.Tests/Endpoints/Jobs/GetJobByIdTests.cs @@ -1,19 +1,19 @@ namespace CleanAspCore.Api.Tests.Endpoints.Jobs; -internal sealed class GetJobByIdTests : TestBase +internal sealed class GetJobByIdTests(TestWebApi sut) { [Test] public async Task GetJobById_ReturnsExpectedJob() { //Arrange var job = new JobFaker().Generate(); - Sut.SeedData(context => + sut.SeedData(context => { context.Jobs.Add(job); }); //Act - var response = await Sut.CreateClientFor().GetJobById(job.Id); + var response = await sut.CreateClientFor().GetJobById(job.Id); //Assert await response.AssertStatusCode(HttpStatusCode.OK); diff --git a/Tests/CleanAspCore.Api.Tests/GlobalSetup.cs b/Tests/CleanAspCore.Api.Tests/GlobalSetup.cs deleted file mode 100644 index 2870b25..0000000 --- a/Tests/CleanAspCore.Api.Tests/GlobalSetup.cs +++ /dev/null @@ -1,35 +0,0 @@ -using CleanAspCore.Api.TestUtils.Logging; -using CleanAspCore.Data; -using CleanAspCore.TestUtils.DataBaseSetup; -using Microsoft.Extensions.DependencyInjection; - -[assembly: FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -[assembly: Parallelizable(ParallelScope.Children)] -[assembly: ExcludeFromCodeCoverage] - -namespace CleanAspCore.Api.Tests; - -[SetUpFixture] -internal sealed class GlobalSetup -{ - internal static IServiceProvider Provider => _serviceProvider; - private static ServiceProvider _serviceProvider = null!; - - [OneTimeSetUp] - public void RunBeforeAnyTests() - { - var services = new ServiceCollection(); - - services.AddLogging(x => x.AddNunitLogging()); - services.RegisterSqlContainer(); - services.AddScoped(); - services.RegisterMigrationInitializer(); - _serviceProvider = services.BuildServiceProvider(); - } - - [OneTimeTearDown] - public async Task RunAfterAnyTests() - { - await _serviceProvider.DisposeAsync(); - } -} diff --git a/Tests/CleanAspCore.Api.Tests/GlobalUsings.cs b/Tests/CleanAspCore.Api.Tests/GlobalUsings.cs index 239820a..39b7825 100644 --- a/Tests/CleanAspCore.Api.Tests/GlobalUsings.cs +++ b/Tests/CleanAspCore.Api.Tests/GlobalUsings.cs @@ -5,4 +5,3 @@ global using CleanAspCore.Api.TestUtils; global using CleanAspCore.Api.TestUtils.Fakers; global using FluentAssertions; -global using NUnit.Framework; diff --git a/Tests/CleanAspCore.Api.Tests/TUnitStartup.cs b/Tests/CleanAspCore.Api.Tests/TUnitStartup.cs new file mode 100644 index 0000000..7576669 --- /dev/null +++ b/Tests/CleanAspCore.Api.Tests/TUnitStartup.cs @@ -0,0 +1,35 @@ +using CleanAspCore.Api.Tests; +using CleanAspCore.Data; +using CleanAspCore.TestUtils.DataBaseSetup; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TUnit.Core.Interfaces; + +[assembly: ClassConstructor] +[assembly: ExcludeFromCodeCoverage] + +namespace CleanAspCore.Api.Tests; + +public class DependencyInjectionClassConstructor : IClassConstructor, ITestEndEventReceiver +{ + private static readonly IServiceProvider _serviceProvider = CreateServiceProvider(); + + private AsyncServiceScope _scope; + + public T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(ClassConstructorMetadata classConstructorMetadata) + where T : class + { + _scope = _serviceProvider.CreateAsyncScope(); + return ActivatorUtilities.GetServiceOrCreateInstance(_scope.ServiceProvider); + } + + public ValueTask OnTestEnd(TestContext testContext) => _scope.DisposeAsync(); + + private static ServiceProvider CreateServiceProvider() => + new ServiceCollection() + .AddLogging(x => x.AddConsole()) + .RegisterSqlContainer() + .RegisterMigrationInitializer() + .AddScoped() + .BuildServiceProvider(); +} diff --git a/Tests/CleanAspCore.Api.Tests/TestSetup/TestBase.cs b/Tests/CleanAspCore.Api.Tests/TestSetup/TestBase.cs deleted file mode 100644 index 2edd249..0000000 --- a/Tests/CleanAspCore.Api.Tests/TestSetup/TestBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace CleanAspCore.Api.Tests.TestSetup; - -internal abstract class TestBase -{ -#pragma warning disable NUnit1032 - protected TestWebApi Sut { get; private set; } = null!; -#pragma warning restore NUnit1032 - - private AsyncServiceScope _scope; - - [SetUp] - public void BeforeTestCase() - { - _scope = GlobalSetup.Provider.CreateAsyncScope(); - Sut = _scope.ServiceProvider.GetRequiredService(); - } - - [TearDown] - public async Task AfterTestCase() - { - await _scope.DisposeAsync(); - } -} diff --git a/Tests/CleanAspCore.Api.Tests/TestSetup/TestWebApi.cs b/Tests/CleanAspCore.Api.Tests/TestSetup/TestWebApi.cs index 5cff326..0b6e640 100644 --- a/Tests/CleanAspCore.Api.Tests/TestSetup/TestWebApi.cs +++ b/Tests/CleanAspCore.Api.Tests/TestSetup/TestWebApi.cs @@ -13,7 +13,7 @@ namespace CleanAspCore.Api.Tests.TestSetup; -internal sealed class TestWebApi : WebApplicationFactory +public sealed class TestWebApi : WebApplicationFactory { private readonly PooledDatabase _pooledDatabase; private readonly ILoggerProvider _loggerProvider; diff --git a/Tests/CleanAspCore.TestUtils/CleanAspCore.TestUtils.csproj b/Tests/CleanAspCore.TestUtils/CleanAspCore.TestUtils.csproj index dfa0be1..2f64fa1 100644 --- a/Tests/CleanAspCore.TestUtils/CleanAspCore.TestUtils.csproj +++ b/Tests/CleanAspCore.TestUtils/CleanAspCore.TestUtils.csproj @@ -4,7 +4,6 @@ - diff --git a/Tests/CleanAspCore.TestUtils/DataBaseSetup/ServiceCollectionExtensions.cs b/Tests/CleanAspCore.TestUtils/DataBaseSetup/ServiceCollectionExtensions.cs index 30534ed..67b1ca4 100644 --- a/Tests/CleanAspCore.TestUtils/DataBaseSetup/ServiceCollectionExtensions.cs +++ b/Tests/CleanAspCore.TestUtils/DataBaseSetup/ServiceCollectionExtensions.cs @@ -13,7 +13,7 @@ public static void RegisterSharedDatabaseServices(this IServiceCollection servic services.AddSingleton(); } - public static void RegisterSqlContainer(this IServiceCollection services) + public static IServiceCollection RegisterSqlContainer(this IServiceCollection services) { services.RegisterSharedDatabaseServices(); services.AddTransient(c => new RespawnerOptions { DbAdapter = DbAdapter.SqlServer }); @@ -26,11 +26,14 @@ public static void RegisterSqlContainer(this IServiceCollection services) container.StartAsync().RunSynchronouslyWithoutSynchronizationContext(); services.AddSingleton(container); + + return services; } - public static void RegisterMigrationInitializer(this IServiceCollection services) + public static IServiceCollection RegisterMigrationInitializer(this IServiceCollection services) where TContext : DbContext, new() { services.AddSingleton>(); + return services; } } diff --git a/Tests/CleanAspCore.TestUtils/Logging/LoggingBuilderExtensions.cs b/Tests/CleanAspCore.TestUtils/Logging/LoggingBuilderExtensions.cs deleted file mode 100644 index c47128f..0000000 --- a/Tests/CleanAspCore.TestUtils/Logging/LoggingBuilderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace CleanAspCore.Api.TestUtils.Logging; - -public static class LoggingBuilderExtensions -{ - public static ILoggingBuilder AddNunitLogging(this ILoggingBuilder services) - { -#pragma warning disable CA2000 - services.AddProvider(new NunitLoggerProvider()); -#pragma warning restore CA2000 - return services; - } -} diff --git a/Tests/CleanAspCore.TestUtils/Logging/NunitLogger.cs b/Tests/CleanAspCore.TestUtils/Logging/NunitLogger.cs deleted file mode 100644 index 22290b1..0000000 --- a/Tests/CleanAspCore.TestUtils/Logging/NunitLogger.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace CleanAspCore.Api.TestUtils.Logging; - -internal sealed class NunitLogger(TextWriter output, string name) : ILogger, IDisposable -{ - public IDisposable? BeginScope(TState state) where TState : notnull => this; - - public bool IsEnabled(LogLevel logLevel) => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - output.WriteLine($"[{DateTime.Now}] {logLevel}: {name}[{eventId.Id}] => {formatter(state, exception)}"); - } - - public void Dispose() { } -} diff --git a/Tests/CleanAspCore.TestUtils/Logging/NunitLoggerProvider.cs b/Tests/CleanAspCore.TestUtils/Logging/NunitLoggerProvider.cs deleted file mode 100644 index e498f46..0000000 --- a/Tests/CleanAspCore.TestUtils/Logging/NunitLoggerProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Extensions.Logging; -using NUnit.Framework; - -namespace CleanAspCore.Api.TestUtils.Logging; - -internal sealed class NunitLoggerProvider : ILoggerProvider -{ - public ILogger CreateLogger(string categoryName) - { - return new NunitLogger(TestContext.Out, categoryName); - } - - public void Dispose() - { - } -}