You've already forked Extensions.Configuration.EntityFrameworkCore
Add happy-path tests
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
// Here you could define global logic that would affect all tests
|
||||
|
||||
// You can use attributes at the assembly level to apply to all tests in the assembly
|
||||
[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
[assembly: NotInParallel]
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RAIC.Extensions.Configuration.EntityFrameworkCore.Extensions;
|
||||
using RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Extensions;
|
||||
using TUnit.Core.Interfaces;
|
||||
|
||||
namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests;
|
||||
|
||||
|
||||
[Table("Settings", Schema = "dbo")]
|
||||
public record Setting : ISetting
|
||||
{
|
||||
[Key]
|
||||
public required string Key { get; set; }
|
||||
|
||||
[Required]
|
||||
public required string Value { get; set; }
|
||||
}
|
||||
|
||||
public class FixtureDbContext(DbContextOptions<FixtureDbContext> options) : DbContext(options), ISettingsDbContext<DbSet<Setting>>, ISettingsDbContextFactory<FixtureDbContext>
|
||||
{
|
||||
public DbSet<Setting> Settings { get; set; }
|
||||
|
||||
public static FixtureDbContext Create(DbContextOptions<FixtureDbContext> options) => new(options);
|
||||
}
|
||||
|
||||
public class FixtureOptions
|
||||
{
|
||||
public required string SomeOption { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class HostFixture: IAsyncInitializer, IServiceScopeFactory
|
||||
{
|
||||
|
||||
private IHost? _host;
|
||||
|
||||
public IServiceScope CreateScope() => _host!.Services.CreateScope();
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var builder = Host.CreateApplicationBuilder();
|
||||
|
||||
// build a initial configuration
|
||||
builder.Configuration
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>()
|
||||
{
|
||||
{ nameof(FixtureOptions.SomeOption), "Memory value" },
|
||||
})
|
||||
.AddUserSecrets<SingleSettingTests>() // for connection string
|
||||
.AddEnvironmentVariables(prefix: "ASPNETCORE_");
|
||||
|
||||
// add DbContext based configuration using connection string sourced from initial config
|
||||
builder.Configuration.AddDbContext<FixtureDbContext>(dbContextOptions => dbContextOptions.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
|
||||
|
||||
builder.Services
|
||||
.AddDbContext<FixtureDbContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("Default")))
|
||||
.AddSqlServerNotificationConfigurationReloadService<FixtureDbContext, Setting>()
|
||||
.AddOptions<FixtureOptions>().Bind(builder.Configuration);
|
||||
|
||||
builder.Logging.AddDebug();
|
||||
|
||||
_host = builder.Build();
|
||||
|
||||
await _host.StartAsync();
|
||||
|
||||
_ = _host.WaitForShutdownAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests where multiple settings get changed in a single transaction
|
||||
/// </summary>
|
||||
[ClassDataSource<HostFixture>(Shared = SharedType.PerClass)]
|
||||
public class MultipleSettingTests
|
||||
{
|
||||
private readonly IServiceScopeFactory _hostScopeFactory;
|
||||
|
||||
public MultipleSettingTests(HostFixture hostFixture)
|
||||
{
|
||||
_hostScopeFactory = hostFixture;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddSettingsTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
context.Settings.AddRange(
|
||||
new() { Key = "BeforeOption", Value = "Foo" },
|
||||
new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" },
|
||||
new() { Key = "AfterOption", Value = "Bar" }
|
||||
);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[DependsOn(nameof(AddSettingsTest))]
|
||||
public async Task UpdateSettingsTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
context.Settings.UpdateRange(
|
||||
new() { Key = "BeforeOption", Value = "Foobar" },
|
||||
new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" },
|
||||
new() { Key = "AfterOption", Value = "Baz" }
|
||||
);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[DependsOn(nameof(UpdateSettingsTest))]
|
||||
public async Task RemoveSettingsTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
await context.Settings.Where(s => s.Key.EndsWith("Option")).ExecuteDeleteAsync();
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<UserSecretsId>cecca246-e624-4ad1-b5a4-7d7b915dfd10</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.*" />
|
||||
<PackageReference Include="TUnit" Version="1.2.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer\RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
# RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests
|
||||
|
||||
Some basic happy path only tests for functionality exposed by `RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.SqlServerNotificationConfigurationReloader`
|
||||
|
||||
## Requirements
|
||||
|
||||
Developers are expected to add their own User Secrets to the test project, containing a default connection string setting
|
||||
(ie. with key `ConnectionStrings:Default`) that points to a pre-configured database that contains a `Settings` table with columns
|
||||
`key` & `value`.
|
||||
|
||||
Additionally, **both** the database and `Settings` table must have change tracking enabled otherwise tests will fail
|
||||
(see the [RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer README](../RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer/README.md) for details of how to
|
||||
do this).
|
||||
|
||||
## TODO
|
||||
* Get EF to drop and create the database itself (using `EnsureDeleted()` & `EnsureCreated()`) instead of
|
||||
forcing this on the user
|
||||
* Additional tests off the happy path ()
|
||||
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Happy path tests
|
||||
/// </summary>
|
||||
[ClassDataSource<HostFixture>(Shared = SharedType.PerClass)]
|
||||
public class SingleSettingTests
|
||||
{
|
||||
private readonly IServiceScopeFactory _hostScopeFactory;
|
||||
|
||||
public SingleSettingTests(HostFixture hostFixture)
|
||||
{
|
||||
_hostScopeFactory = hostFixture;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddSettingTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
context.Settings.Add(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" });
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[DependsOn(nameof(AddSettingTest))]
|
||||
public async Task UpdateSettingTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
context.Settings.Update(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" });
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[DependsOn(nameof(UpdateSettingTest))]
|
||||
public async Task RemoveSettingTest()
|
||||
{
|
||||
// Pre-condition sanity check!
|
||||
using (var preconditionScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated");
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var actScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var context = actScope.ServiceProvider.GetRequiredService<FixtureDbContext>();
|
||||
await context.Settings.Where(s => s.Key == nameof(FixtureOptions.SomeOption)).ExecuteDeleteAsync();
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle
|
||||
|
||||
// Assert
|
||||
using (var assertScope = _hostScopeFactory.CreateScope())
|
||||
{
|
||||
var finalOptionsState = assertScope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FixtureOptions>>().Value;
|
||||
await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user