Skip to content

Commit

Permalink
Update Logging and Testing projects, and add EntityFrameworkCore project
Browse files Browse the repository at this point in the history
Multiple changes have been made: An EntityFrameworkCore project has been introduced with a DbContextBuilder class, useful for constructing an instance of DbContext. The Logging project has been updated with the introduction of TestLoggerSettings to better control the logging level. In addition, various updates have been made to our Testing project, including modifications in test run timing and in the output format of our logs.
  • Loading branch information
frankhaugen committed Jan 21, 2024
1 parent 42a7ce7 commit 255ae84
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 12 deletions.
64 changes: 64 additions & 0 deletions Frank.Testing.EntityFrameworkCore/DbContextBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Frank.Testing.EntityFrameworkCore;

/// <summary>
/// Builder class for constructing an instance of DbContext.
/// </summary>
/// <typeparam name="T">The type of DbContext.</typeparam>
public class DbContextBuilder<T> where T : DbContext
{
private readonly IServiceCollection _serviceCollection = new ServiceCollection();

private Action<DbContextOptionsBuilder>? _configuredOptions;

private ILoggerFactory? _loggerFactory;
private string _sqliteConnectionString = "Data Source=:memory:";

public DbContextBuilder<T> WithLoggerProvider(ILoggerProvider loggerProvider)
{
_loggerFactory = LoggerFactory.Create(builder => builder.ClearProviders().AddProvider(loggerProvider));
return this;
}

public DbContextBuilder<T> WithSqliteConnectionString(string sqliteConnectionString)
{
_sqliteConnectionString = sqliteConnectionString;
return this;
}

public DbContextBuilder<T> WithService<TService>(Action<IServiceCollection> configureService)
{
configureService(_serviceCollection);
return this;
}

public DbContextBuilder<T> WithOptions(Action<DbContextOptionsBuilder<T>> configureOptions)
{
_configuredOptions = configureOptions as Action<DbContextOptionsBuilder>;
return this;
}

public T Build()
{
_serviceCollection.AddDbContext<T>(OptionsAction);
var context = _serviceCollection.BuildServiceProvider().GetRequiredService<T>();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
return context;
}

private void OptionsAction(IServiceProvider arg1, DbContextOptionsBuilder arg2)
{
_configuredOptions?.Invoke(arg2);
if (_loggerFactory != null)
arg2.UseLoggerFactory(_loggerFactory);

arg2.EnableSensitiveDataLogging();
arg2.EnableDetailedErrors();
arg2.UseSqlite(_sqliteConnectionString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>xUnit.net helpers and extensions for writing automated tests</Description>
<PackageTags>xunit test testing TDD BDD</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Frank.Testing.Logging/Frank.Testing.Logging.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageReference Include="Frank.Reflection" Version="1.1.0" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.5" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.6" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Frank.Testing.Logging/LoggingBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuild
options.LogLevel = logLevel;
});
builder.Services.AddPulseFlow(flowBuilder => flowBuilder.AddFlow<TestLoggingOutputFlow>());
builder.Services.AddSingleton<ILoggerProvider>(provider => new TestLoggerProvider(provider.GetRequiredService<IConduit>()));
builder.Services.AddSingleton<ILoggerProvider>(provider => new TestLoggerProvider(provider.GetRequiredService<IConduit>(), provider.GetService<TestLoggerSettings>() ?? new TestLoggerSettings()));
return builder;
}
}
6 changes: 3 additions & 3 deletions Frank.Testing.Logging/PulseFlowTestLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

namespace Frank.Testing.Logging;

public class PulseFlowTestLogger(IConduit conduit, string categoryName) : ILogger
public class PulseFlowTestLogger(IConduit conduit, string categoryName, TestLoggerSettings settings) : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
=> conduit.SendAsync(new LogPulse(logLevel, eventId, exception, categoryName, formatter(state, exception))).GetAwaiter().GetResult();

public bool IsEnabled(LogLevel logLevel) => true;
public bool IsEnabled(LogLevel logLevel) => logLevel >= settings.LogLevel;

public IDisposable? BeginScope<TState>(TState state) where TState : notnull => new PulseFlowLoggerScope<TState>(state);
}

public class PulseFlowTestLogger<T>(IConduit conduit) : PulseFlowTestLogger(conduit, typeof(T).GetDisplayName()), ILogger<T>;
public class PulseFlowTestLogger<T>(IConduit conduit, TestLoggerSettings settings) : PulseFlowTestLogger(conduit, typeof(T).GetDisplayName(), settings), ILogger<T>;
4 changes: 2 additions & 2 deletions Frank.Testing.Logging/TestLoggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Frank.Testing.Logging;

public class TestLoggerProvider(IConduit conduit) : ILoggerProvider
public class TestLoggerProvider(IConduit conduit, TestLoggerSettings settings) : ILoggerProvider
{
private readonly ConcurrentDictionary<string, ILogger> _loggers = new();

Expand All @@ -15,7 +15,7 @@ public ILogger CreateLogger(string categoryName)
if (_loggers.TryGetValue(categoryName, out var logger))
return logger;

var newLogger = new PulseFlowTestLogger(conduit, categoryName);
var newLogger = new PulseFlowTestLogger(conduit, categoryName, settings);
return _loggers.GetOrAdd(categoryName, newLogger);
}

Expand Down
2 changes: 1 addition & 1 deletion Frank.Testing.Logging/TestLoggingOutputFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public async Task HandleAsync(IPulse pulse, CancellationToken cancellationToken)
{
if (pulse is LogPulse logPulse)
{
outputHelper.WriteLine(logPulse.Message);
outputHelper.WriteLine(logPulse.ToString());
await Task.CompletedTask;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="VarDump" Version="0.2.12" />
<PackageReference Include="VarDump" Version="0.2.14" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="Xunit.Extensions.Ordering" Version="1.4.5" />
</ItemGroup>
Expand Down
111 changes: 111 additions & 0 deletions Frank.Testing.Tests/EntityFramworkCoreTests/DbContextBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Frank.PulseFlow;
using Frank.PulseFlow.Logging;
using Frank.Testing.EntityFrameworkCore;
using Frank.Testing.Logging;
using Frank.Testing.Tests.TestingInfrastructure;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

using Xunit.Abstractions;

namespace Frank.Testing.Tests.EntityFramworkCoreTests;

public class DbContextBuilderTests(ITestOutputHelper outputHelper)
{
[Fact]
public void Build_WithLoggerProvider_UsesLoggerProvider()
{
var conduit = new TestConduit();
var loggerProvider = new TestLoggerProvider(conduit, new TestLoggerSettings());
var dbContext = new DbContextBuilder<DbContext>()
.WithLoggerProvider(loggerProvider)
.Build();
dbContext.Database.EnsureCreated();
dbContext.Database.ExecuteSqlRaw("SELECT 1");
dbContext.Dispose();

outputHelper.WriteJson(conduit.Logs);
// var log = conduit.Logs.Single();
// Assert.Equal("SELECT 1", log.Message);
}

[Fact]
public void Build_WithService_UsesService()
{
var dbContext = new DbContextBuilder<DbContext>()
.WithService<ITestService>(services => services.AddSingleton<ITestService, TestService>())
.Build();
Assert.NotNull(dbContext.GetService<ITestService>());
}

[Fact]
public void Build_WithOptions_UsesOptions()
{
var dbContext = new DbContextBuilder<DbContext>()
.WithOptions(options => options.UseSqlite("Data Source=:memory:"))
.Build();
dbContext.Database.EnsureCreated();
dbContext.Database.ExecuteSqlRaw("SELECT 1");
dbContext.Dispose();
}

[Fact]
public void Build_WithLoggerProviderAndService_UsesLoggerProviderAndService()
{
var conduit = new TestConduit();
var loggerProvider = new TestLoggerProvider(conduit, new TestLoggerSettings());
var dbContext = new DbContextBuilder<TestDbContext>()
.WithLoggerProvider(loggerProvider)
.WithSqliteConnectionString("Data Source=MyTestDatabase.db")
.WithService<ITestService>(services => services.AddSingleton<ITestService, TestService>())
.Build();
dbContext.Database.EnsureCreated();
dbContext.Persons.Add(new TestPerson() { Name = "Frank" });
dbContext.SaveChanges();
dbContext.Dispose();
}

public class TestService : ITestService
{
public async Task DoSomethingAsync()
{
await Task.CompletedTask;
}
}

public interface ITestService
{
Task DoSomethingAsync();
}

[Fact]
public void Build_WithLoggerProviderAndOptions_UsesLoggerProviderAndOptions()
{
var conduit = new TestConduit();
var loggerProvider = new TestLoggerProvider(conduit, new TestLoggerSettings());
var dbContext = new DbContextBuilder<TestDbContext>()
.WithSqliteConnectionString("Data Source=MyTestDatabase.db")
.WithLoggerProvider(loggerProvider)
.Build();
dbContext.Database.EnsureCreated();
dbContext.Persons.Add(new TestPerson() { Name = "Frank" });
dbContext.SaveChanges();
dbContext.Database.ExecuteSqlRaw("SELECT 1");
dbContext.Dispose();

outputHelper.WriteJson(conduit.Logs);
}

public class TestConduit : IConduit
{
public async Task SendAsync(IPulse message)
{
await Task.CompletedTask;
Logs.Add(message as LogPulse ?? throw new InvalidOperationException());
}

public List<LogPulse> Logs { get; } = new();
}
}
3 changes: 2 additions & 1 deletion Frank.Testing.Tests/Frank.Testing.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand All @@ -27,6 +27,7 @@

<ItemGroup>
<ProjectReference Include="..\Frank.Testing.ApiTesting\Frank.Testing.ApiTesting.csproj" />
<ProjectReference Include="..\Frank.Testing.EntityFrameworkCore\Frank.Testing.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\Frank.Testing.Logging\Frank.Testing.Logging.csproj" />
<ProjectReference Include="..\Frank.Testing.TestOutputExtensions\Frank.Testing.TestOutputExtensions.csproj" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Frank.Testing.Tests/TestLogging/TestLoggingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class TestLoggingTests(ITestOutputHelper outputHelper)
[Fact]
public async Task Test1()
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var host = CreateHostBuilder().Build();

await host.RunAsync(cancellationTokenSource.Token);
Expand All @@ -40,7 +40,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Hello from MyService");
await Task.Delay(1000, stoppingToken);
await Task.Delay(100, stoppingToken);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Frank.Testing.Tests/TestingInfrastructure/TestAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

public class TestAddress
{
public Guid Id { get; set; }

public string City { get; set; }

public int ZipCode { get; set; }
Expand Down
21 changes: 21 additions & 0 deletions Frank.Testing.Tests/TestingInfrastructure/TestDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;

namespace Frank.Testing.Tests.TestingInfrastructure;

public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}

public DbSet<TestPerson> Persons { get; set; }

public DbSet<TestAddress> Addresses { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TestPerson>().HasKey(e => e.Id);
modelBuilder.Entity<TestAddress>().HasKey(e => e.Id);
base.OnModelCreating(modelBuilder);
}
}
1 change: 1 addition & 0 deletions Frank.Testing.Tests/TestingInfrastructure/TestPerson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class TestPerson
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }

Expand Down
6 changes: 6 additions & 0 deletions Frank.Testing.sln
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.TestOutputExt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.ApiTesting", "Frank.Testing.ApiTesting\Frank.Testing.ApiTesting.csproj", "{3B30D75C-5B95-4E87-B862-7E8DC49038DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.EntityFrameworkCore", "Frank.Testing.EntityFrameworkCore\Frank.Testing.EntityFrameworkCore.csproj", "{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -47,5 +49,9 @@ Global
{3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B30D75C-5B95-4E87-B862-7E8DC49038DF}.Release|Any CPU.Build.0 = Release|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

0 comments on commit 255ae84

Please sign in to comment.