feat: add integration tests

This commit is contained in:
Jean Gatto
2023-08-27 12:19:43 -03:00
parent 5e55bc847c
commit 467e2df644
16 changed files with 387 additions and 58 deletions
+4 -1
View File
@@ -3,6 +3,8 @@
<PropertyGroup Label="Build">
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- Disable overriding package versions -->
<!-- https://devblogs.microsoft.com/nuget/introducing-central-package-management/#overriding-package-versions -->
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
@@ -22,6 +24,7 @@
<PackageVersion Include="FluentValidation" Version="11.7.1" />
<PackageVersion Include="MediatR" Version="12.1.1" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
@@ -58,7 +61,7 @@
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.16" />
<PackageVersion Include="NSubstitute" Version="5.0.0" />
<PackageVersion Include="Polly" Version="7.2.4" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.4.0" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.5.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.8.0.76515" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
+8 -1
View File
@@ -28,7 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
global.json = global.json
Directory.Packages.props = Directory.Packages.props
requests.http = requests.http
requests.http = requests.http
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shop.Query", "src\Shop.Query\Shop.Query.csproj", "{C2A82AAD-0348-4B29-B7E7-E36509EE068C}"
@@ -48,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\sonar-cloud.yml = .github\workflows\sonar-cloud.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shop.IntegrationTests", "tests\Shop.IntegrationTests\Shop.IntegrationTests.csproj", "{7BA06288-8446-4099-9341-D213A835763C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -85,6 +87,10 @@ Global
{C2A82AAD-0348-4B29-B7E7-E36509EE068C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2A82AAD-0348-4B29-B7E7-E36509EE068C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2A82AAD-0348-4B29-B7E7-E36509EE068C}.Release|Any CPU.Build.0 = Release|Any CPU
{7BA06288-8446-4099-9341-D213A835763C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BA06288-8446-4099-9341-D213A835763C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BA06288-8446-4099-9341-D213A835763C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BA06288-8446-4099-9341-D213A835763C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D957E297-38EA-402F-B603-25F3EC156E18} = {DFB189FE-F11B-46EA-A5CD-3571CEC8193A}
@@ -96,5 +102,6 @@ Global
{C2A82AAD-0348-4B29-B7E7-E36509EE068C} = {8011CDDD-0814-4C74-9B74-C3E3365D50CC}
{E94C4296-DC64-4C22-87A1-B5A054FAADAC} = {8011CDDD-0814-4C74-9B74-C3E3365D50CC}
{11296DB2-D948-4ADF-A4CE-3F2BFF627478} = {8011CDDD-0814-4C74-9B74-C3E3365D50CC}
{7BA06288-8446-4099-9341-D213A835763C} = {DFB189FE-F11B-46EA-A5CD-3571CEC8193A}
EndGlobalSection
EndGlobal
@@ -20,5 +20,5 @@ public sealed class ConnectionOptions : IAppOptions
[Required]
public string CacheConnection { get; private init; }
public bool CacheConnectionInMemory() => SqlConnection.Equals("InMemory", ComparisonType);
public bool CacheConnectionInMemory() => CacheConnection.Equals("InMemory", ComparisonType);
}
@@ -16,7 +16,7 @@ public class WriteDbContext : BaseDbContext<WriteDbContext>
public WriteDbContext(DbContextOptions<WriteDbContext> dbOptions) : base(dbOptions) =>
_encryptionProvider = new AesProvider(EncryptionKey, EncryptionIv);
internal DbSet<Customer> Customers => Set<Customer>();
public DbSet<Customer> Customers => Set<Customer>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -15,19 +15,16 @@ internal class CustomerConfiguration : IEntityTypeConfiguration<Customer>
builder
.Property(customer => customer.FirstName)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasMaxLength(100);
builder
.Property(customer => customer.LastName)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasMaxLength(100);
builder
.Property(customer => customer.Gender)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasMaxLength(6)
.HasConversion<string>();
@@ -37,7 +34,6 @@ internal class CustomerConfiguration : IEntityTypeConfiguration<Customer>
ownedNav
.Property(email => email.Address)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasMaxLength(254)
.HasColumnName(nameof(Customer.Email))
.IsEncrypted(); // Encrypted Column, ref: https://github.com/Eastrall/EntityFrameworkCore.DataEncryption
@@ -23,14 +23,11 @@ internal class EventStoreConfiguration : IEntityTypeConfiguration<EventStore>
builder
.Property(eventStore => eventStore.MessageType)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasMaxLength(100);
builder
.Property(eventStore => eventStore.Data)
.IsRequired() // NOT NULL
.IsUnicode(false) // VARCHAR
.HasColumnType("VARCHAR(MAX)")
.HasComment("JSON serialized event");
builder
@@ -13,7 +13,7 @@ using Shop.PublicApi.Models;
using Shop.Query.Application.Customer.Queries;
using Shop.Query.QueriesModel;
namespace Shop.PublicApi.Controllers;
namespace Shop.PublicApi.Controllers.V1;
[ApiController]
[ApiVersion("1.0")]
@@ -3,7 +3,7 @@ using Shop.PublicApi.Middlewares;
namespace Shop.PublicApi.Extensions;
public static class MiddlewareExtensions
internal static class MiddlewareExtensions
{
public static void UseCorrelationId(this IApplicationBuilder builder) =>
builder.UseMiddleware<CorrelationIdMiddleware>();
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -12,7 +13,35 @@ namespace Shop.PublicApi.Extensions;
internal static class WebApplicationExtensions
{
public static async Task MigrateDbAsync(this WebApplication app, AsyncServiceScope serviceScope)
public static async Task RunAppAsync(this WebApplication app)
{
await using var serviceScope = app.Services.CreateAsyncScope();
var mapper = serviceScope.ServiceProvider.GetRequiredService<IMapper>();
app.Logger.LogInformation("----- AutoMapper: mappings are being validated...");
// Validate the AutoMapper configuration by asserting that the mappings are valid
mapper.ConfigurationProvider.AssertConfigurationIsValid();
// Compile the AutoMapper mappings for better performance
mapper.ConfigurationProvider.CompileMappings();
app.Logger.LogInformation("----- AutoMapper: mappings are valid!");
app.Logger.LogInformation("----- Databases are being migrated....");
// Migrate the databases asynchronously using the provided service scope
await app.MigrateDataBasesAsync(serviceScope);
app.Logger.LogInformation("----- Databases have been successfully migrated!");
app.Logger.LogInformation("----- Application is starting....");
await app.RunAsync();
}
private static async Task MigrateDataBasesAsync(this WebApplication app, AsyncServiceScope serviceScope)
{
await using var writeDbContext = serviceScope.ServiceProvider.GetRequiredService<WriteDbContext>();
await using var eventStoreDbContext = serviceScope.ServiceProvider.GetRequiredService<EventStoreDbContext>();
+32 -3
View File
@@ -1,11 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
namespace Shop.PublicApi.Models;
public sealed class ApiErrorResponse
{
[JsonConstructor]
public ApiErrorResponse(string message) => Message = message;
public string Message { get; }
@@ -15,9 +17,22 @@ public sealed class ApiErrorResponse
public class ApiResponse
{
public bool Success { get; protected init; }
public string SuccessMessage { get; protected init; }
public int StatusCode { get; protected init; }
[JsonConstructor]
public ApiResponse(bool success, string successMessage, int statusCode, IEnumerable<ApiErrorResponse> errors)
{
Success = success;
SuccessMessage = successMessage;
StatusCode = statusCode;
Errors = errors;
}
public ApiResponse()
{
}
public bool Success { get; protected set; }
public string SuccessMessage { get; protected set; }
public int StatusCode { get; protected set; }
public IEnumerable<ApiErrorResponse> Errors { get; private init; } = Enumerable.Empty<ApiErrorResponse>();
public static ApiResponse Ok() =>
@@ -70,10 +85,24 @@ public class ApiResponse
private static IEnumerable<ApiErrorResponse> CreateErrors(string errorMessage) =>
new[] { new ApiErrorResponse(errorMessage) };
public override string ToString() =>
$"Success: {Success} | StatusCode: {StatusCode} | HasErrors: {Errors.Any()}";
}
public sealed class ApiResponse<T> : ApiResponse
{
[JsonConstructor]
public ApiResponse(T result, bool success, string successMessage, int statusCode, IEnumerable<ApiErrorResponse> errors)
: base(success, successMessage, statusCode, errors)
{
Result = result;
}
public ApiResponse()
{
}
public T Result { get; private init; }
public static ApiResponse<T> Ok(T result) =>
+5 -24
View File
@@ -1,6 +1,5 @@
using System.Globalization;
using System.IO.Compression;
using AutoMapper;
using FluentValidation;
using FluentValidation.Resources;
using Microsoft.AspNetCore.Builder;
@@ -10,7 +9,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -24,8 +22,7 @@ using StackExchange.Profiling;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.Configure<KestrelServerOptions>(kestrelOptions => kestrelOptions.AddServerHeader = false)
.Configure<GzipCompressionProviderOptions>(compressionOptions => compressionOptions.Level = CompressionLevel.Optimal)
.Configure<GzipCompressionProviderOptions>(compressionOptions => compressionOptions.Level = CompressionLevel.Fastest)
.Configure<JsonOptions>(jsonOptions => jsonOptions.JsonSerializerOptions.Configure())
.Configure<RouteOptions>(routeOptions => routeOptions.LowercaseUrls = true);
@@ -134,24 +131,8 @@ app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await using var serviceScope = app.Services.CreateAsyncScope();
var mapper = serviceScope.ServiceProvider.GetRequiredService<IMapper>();
await app.RunAppAsync();
app.Logger.LogInformation("----- AutoMapper: mappings are being validated...");
// Dry run all configured type maps and throw AutoMapper.AutoMapperConfigurationException for each problem.
mapper.ConfigurationProvider.AssertConfigurationIsValid();
// Compile all underlying mapping expressions to cached delegates.
mapper.ConfigurationProvider.CompileMappings();
app.Logger.LogInformation("----- AutoMapper: mappings are valid!");
app.Logger.LogInformation("----- Databases are being migrated....");
await app.MigrateDbAsync(serviceScope);
app.Logger.LogInformation("----- Databases have been successfully migrated!");
app.Logger.LogInformation("----- Application is starting....");
await app.RunAsync();
#pragma warning disable S1118
public partial class Program { }
#pragma warning restore S1118
+2 -1
View File
@@ -58,8 +58,9 @@
</ItemGroup>
<ItemGroup Label="Unit Tests">
<InternalsVisibleTo Include="Shop.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
<InternalsVisibleTo Include="Shop.IntegrationTests" />
<InternalsVisibleTo Include="Shop.UnitTests" />
</ItemGroup>
</Project>
@@ -59,23 +59,30 @@ public static class ServicesCollectionExtensions
/// </summary>
private static void ConfigureMongoDb()
{
// Step 1: Configure the serializer for Guid type.
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy));
try
{
// Step 1: Configure the serializer for Guid type.
BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy));
// Step 2: Configure the conventions to be applied to all mappings.
// REF: https://mongodb.github.io/mongo-csharp-driver/2.0/reference/bson/mapping/conventions/
ConventionRegistry.Register("Conventions",
new ConventionPack
{
new CamelCaseElementNameConvention(), // Convert element names to camel case
new EnumRepresentationConvention(BsonType.String), // Serialize enums as strings
new IgnoreExtraElementsConvention(true), // Ignore extra elements when deserializing
new IgnoreIfNullConvention(true) // Ignore null values when serializing
}, _ => true);
// Step 2: Configure the conventions to be applied to all mappings.
// REF: https://mongodb.github.io/mongo-csharp-driver/2.0/reference/bson/mapping/conventions/
ConventionRegistry.Register("Conventions",
new ConventionPack
{
new CamelCaseElementNameConvention(), // Convert element names to camel case
new EnumRepresentationConvention(BsonType.String), // Serialize enums as strings
new IgnoreExtraElementsConvention(true), // Ignore extra elements when deserializing
new IgnoreIfNullConvention(true) // Ignore null values when serializing
}, _ => true);
// Step 3: Register the mappings configurations.
// It is recommended to register all mappings before initializing the connection with MongoDb
// REF: https://mongodb.github.io/mongo-csharp-driver/2.0/reference/bson/mapping/
new CustomerMap().Configure(); // Configuration for Customer class
// Step 3: Register the mappings configurations.
// It is recommended to register all mappings before initializing the connection with MongoDb
// REF: https://mongodb.github.io/mongo-csharp-driver/2.0/reference/bson/mapping/
new CustomerMap().Configure(); // Configuration for Customer class
}
catch
{
// Ignore
}
}
}
@@ -0,0 +1,221 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Bogus;
using FluentAssertions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using NSubstitute;
using Shop.Application.Customer.Commands;
using Shop.Application.Customer.Responses;
using Shop.Core.Extensions;
using Shop.Domain.Entities.CustomerAggregate;
using Shop.Domain.ValueObjects;
using Shop.Infrastructure.Data.Context;
using Shop.PublicApi.Models;
using Shop.Query.Abstractions;
using Shop.Query.Data.Context;
using Xunit;
using Xunit.Categories;
namespace Shop.IntegrationTests.Controllers.V1;
[IntegrationTest]
public class CustomersControllerTests : IAsyncLifetime
{
private const string ConnectionString = "Data Source=:memory:";
private const string Endpoint = "/api/customers";
private readonly SqliteConnection _eventStoreDbContextSqlite = new(ConnectionString);
private readonly SqliteConnection _writeDbContextSqlite = new(ConnectionString);
[Fact]
public async Task Should_ReturnsHttpStatus200Ok_When_Post_ValidRequest()
{
// Arrange
await using var webApplicationFactory = InitializeWebAppFactory();
using var httpClient = webApplicationFactory.CreateClient(CreateClientOptions());
var command = new Faker<CreateCustomerCommand>()
.RuleFor(command => command.FirstName, faker => faker.Person.FirstName)
.RuleFor(command => command.LastName, faker => faker.Person.LastName)
.RuleFor(command => command.Email, faker => faker.Person.Email)
.RuleFor(command => command.Gender, faker => faker.PickRandom<EGender>())
.RuleFor(command => command.DateOfBirth, faker => faker.Person.DateOfBirth)
.Generate();
var commandAsJsonString = command.ToJson();
// Act
using var jsonContent = new StringContent(commandAsJsonString, Encoding.UTF8, MediaTypeNames.Application.Json);
using var act = await httpClient.PostAsync(Endpoint, jsonContent);
// Assert (Http)
act.Should().NotBeNull();
act.IsSuccessStatusCode.Should().BeTrue();
act.StatusCode.Should().Be(HttpStatusCode.OK);
// Assert (Http Content Response)
var response = (await act.Content.ReadAsStringAsync()).FromJson<ApiResponse<CreatedCustomerResponse>>();
response.Should().NotBeNull();
response.Success.Should().BeTrue();
response.StatusCode.Should().Be(StatusCodes.Status200OK);
response.Result.Should().NotBeNull();
response.Result.Id.Should().NotBeEmpty();
response.Errors.Should().BeEmpty();
}
[Fact]
public async Task Should_ReturnsHttpStatus400BadRequest_When_Post_InvalidRequest()
{
// Arrange
await using var webApplicationFactory = InitializeWebAppFactory();
using var httpClient = webApplicationFactory.CreateClient(CreateClientOptions());
var command = new Faker<CreateCustomerCommand>().Generate();
var commandAsJsonString = command.ToJson();
// Act
using var jsonContent = new StringContent(commandAsJsonString, Encoding.UTF8, MediaTypeNames.Application.Json);
using var act = await httpClient.PostAsync(Endpoint, jsonContent);
// Assert (Http)
act.Should().NotBeNull();
act.IsSuccessStatusCode.Should().BeFalse();
act.StatusCode.Should().Be(HttpStatusCode.BadRequest);
// Assert (Http Content Response)
var response = (await act.Content.ReadAsStringAsync()).FromJson<ApiResponse<CreatedCustomerResponse>>();
response.Should().NotBeNull();
response.Success.Should().BeFalse();
response.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
response.Result.Should().BeNull();
response.Errors.Should().NotBeNullOrEmpty().And.OnlyHaveUniqueItems();
}
[Fact]
public async Task Should_ReturnsHttpStatus400BadRequest_When_Post_EmailAddressIsAlready()
{
// Arrange
var customer = new Faker<Customer>()
.CustomInstantiator(faker =>
{
var emailResult = Email.Create(faker.Person.Email);
return new Customer(
faker.Person.FirstName,
faker.Person.LastName,
faker.PickRandom<EGender>(),
emailResult.Value,
faker.Person.DateOfBirth);
})
.Generate();
await using var webApplicationFactory = InitializeWebAppFactory((serviceScope) =>
{
var writeDbContext = serviceScope.ServiceProvider.GetRequiredService<WriteDbContext>();
writeDbContext.Customers.Add(customer);
writeDbContext.SaveChanges();
});
using var httpClient = webApplicationFactory.CreateClient(CreateClientOptions());
var command = new Faker<CreateCustomerCommand>()
.RuleFor(command => command.FirstName, faker => faker.Person.FirstName)
.RuleFor(command => command.LastName, faker => faker.Person.LastName)
.RuleFor(command => command.Email, customer.Email.Address)
.RuleFor(command => command.Gender, faker => faker.PickRandom<EGender>())
.RuleFor(command => command.DateOfBirth, faker => faker.Person.DateOfBirth)
.Generate();
var commandAsJsonString = command.ToJson();
// Act
using var jsonContent = new StringContent(commandAsJsonString, Encoding.UTF8, MediaTypeNames.Application.Json);
using var act = await httpClient.PostAsync(Endpoint, jsonContent);
// Assert (Http)
act.Should().NotBeNull();
act.IsSuccessStatusCode.Should().BeFalse();
act.StatusCode.Should().Be(HttpStatusCode.BadRequest);
// Assert (Http Content Response)
var response = (await act.Content.ReadAsStringAsync()).FromJson<ApiResponse<CreatedCustomerResponse>>();
response.Should().NotBeNull();
response.Success.Should().BeFalse();
response.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
response.Result.Should().BeNull();
response.Errors.Should().NotBeNullOrEmpty()
.And.OnlyHaveUniqueItems()
.And.AllSatisfy(error => error.Message.Should().Be("The provided email address is already in use."));
}
#region IAsyncLifetime
public async Task InitializeAsync()
{
await _writeDbContextSqlite.OpenAsync();
await _eventStoreDbContextSqlite.OpenAsync();
}
public async Task DisposeAsync()
{
await _writeDbContextSqlite.DisposeAsync();
await _eventStoreDbContextSqlite.DisposeAsync();
}
#endregion
private WebApplicationFactory<Program> InitializeWebAppFactory(Action<IServiceScope> configureServices = null)
{
return new WebApplicationFactory<Program>()
.WithWebHostBuilder(hostBuilder =>
{
hostBuilder.UseSetting("ConnectionStrings:SqlConnection", "InMemory");
hostBuilder.UseSetting("ConnectionStrings:NoSqlConnection", "InMemory");
hostBuilder.UseSetting("ConnectionStrings:CacheConnection", "InMemory");
hostBuilder.UseSetting("CacheOptions:AbsoluteExpirationInHours", "1");
hostBuilder.UseSetting("CacheOptions:SlidingExpirationInSeconds", "30");
hostBuilder.UseEnvironment(Environments.Production);
hostBuilder.ConfigureServices(services =>
{
services.RemoveAll<WriteDbContext>();
services.RemoveAll<DbContextOptions<WriteDbContext>>();
services.RemoveAll<EventStoreDbContext>();
services.RemoveAll<DbContextOptions<EventStoreDbContext>>();
services.RemoveAll<NoSqlDbContext>();
services.RemoveAll<ISynchronizeDb>();
services.AddDbContext<WriteDbContext>(options => options.UseSqlite(_writeDbContextSqlite));
services.AddDbContext<EventStoreDbContext>(options => options.UseSqlite(_eventStoreDbContextSqlite));
services.AddSingleton(_ => Substitute.For<IReadDbContext>());
services.AddSingleton(_ => Substitute.For<ISynchronizeDb>());
var serviceProvider = services.BuildServiceProvider(true);
using var serviceScope = serviceProvider.CreateScope();
var writeDbContext = serviceScope.ServiceProvider.GetRequiredService<WriteDbContext>();
writeDbContext.Database.EnsureCreated();
var eventStoreDbContext = serviceScope.ServiceProvider.GetRequiredService<EventStoreDbContext>();
eventStoreDbContext.Database.EnsureCreated();
configureServices?.Invoke(serviceScope);
writeDbContext.Dispose();
eventStoreDbContext.Dispose();
});
});
}
private static WebApplicationFactoryClientOptions CreateClientOptions() =>
new() { BaseAddress = new Uri("https://127.0.0.1"), AllowAutoRedirect = false };
}
@@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Ardalis.Result" />
<PackageReference Include="AutoMapper" />
<PackageReference Include="Bogus" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="FluentValidation" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="NSubstitute.Analyzers.CSharp">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.analyzers" />
<PackageReference Include="xunit.categories" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\..\src\Shop.PublicApi\Shop.PublicApi.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"preEnumerateTheories": true
}