-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
364 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
content/Coalesce.Starter.Vue.Data.Test/Coalesce.Starter.Vue.Data.Test.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="coverlet.collector" Version="6.0.2"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> | ||
<PackageReference Include="xunit" Version="2.8.0" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Moq.AutoMock" Version="3.5.0" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Coalesce.Starter.Vue.Data\Coalesce.Starter.Vue.Data.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Using Include="Xunit" /> | ||
<Using Include="Coalesce.Starter.Vue.Data" /> | ||
<Using Include="Coalesce.Starter.Vue.Data.Models" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
namespace Coalesce.Starter.Vue.Data.Test; | ||
|
||
public class UnitTest1 : TestBase | ||
{ | ||
[Fact] | ||
public void Test1() | ||
{ | ||
// Arrange | ||
var widget1 = new Widget { Name = "Gnoam Sprecklesprocket", Category = WidgetCategory.Sprecklesprockets }; | ||
Db.Add(widget1); | ||
Db.SaveChanges(); | ||
|
||
RefreshServices(); | ||
|
||
// Act | ||
var widget2 = Db.Widgets.Single(); | ||
|
||
// Assert | ||
Assert.Equal(WidgetCategory.Sprecklesprockets, widget2.Category); | ||
|
||
// After calling RefreshServices, we have a different DbContext instance | ||
// and so we'll get a different entity instance. | ||
Assert.NotEqual(widget1, widget2); | ||
|
||
|
||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
content/Coalesce.Starter.Vue.Data.Test/Utilities/AppDbContextForSqlite.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||
|
||
namespace Coalesce.Starter.Vue.Data.Test; | ||
|
||
public class AppDbContextForSqlite(DbContextOptions<AppDbContext> options) : AppDbContext(options) | ||
{ | ||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) | ||
{ | ||
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations | ||
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations | ||
configurationBuilder.Properties<DateTimeOffset>().HaveConversion<DateTimeOffsetToStringConverter>(); | ||
configurationBuilder.Properties<DateOnly>().HaveConversion<DateOnlyToDateTimeConverter>(); | ||
} | ||
|
||
public class DateOnlyToDateTimeConverter : ValueConverter<DateOnly, DateTime> | ||
{ | ||
public DateOnlyToDateTimeConverter() | ||
: base( | ||
dateOnly => dateOnly.ToDateTime(new TimeOnly()), | ||
dateTime => DateOnly.FromDateTime(dateTime) | ||
) | ||
{ } | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
content/Coalesce.Starter.Vue.Data.Test/Utilities/AssertionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using IntelliTect.Coalesce.Models; | ||
|
||
namespace Coalesce.Starter.Vue.Data.Test; | ||
|
||
public static class AssertionExtensions | ||
{ | ||
/// <summary> | ||
/// Asserts that the result was a failure. | ||
/// </summary> | ||
public static void AssertError(this ApiResult result) | ||
{ | ||
Assert.False(result.WasSuccessful); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was a failure. | ||
/// </summary> | ||
/// <param name="message">Expected error message.</param> | ||
public static void AssertError(this ApiResult result, string message) | ||
{ | ||
result.AssertError(); | ||
Assert.Equal(message, result.Message); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was successful. | ||
/// </summary> | ||
public static void AssertSuccess(this ApiResult result, string? message = null) | ||
{ | ||
// Returns a more useful assertion error than only checking WasSuccessful. | ||
Assert.Equal(message, result.Message); | ||
Assert.True(result.WasSuccessful); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was successful. | ||
/// </summary> | ||
public static T AssertSuccess<T>(this ItemResult<T> result) | ||
{ | ||
Assert.Null(result.Message); | ||
Assert.True(result.WasSuccessful); | ||
return result.Object ?? throw new ArgumentException("Sucessful result unexpectedly returned null object"); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was successful. | ||
/// </summary> | ||
public static async Task<T> AssertSuccess<T>(this Task<ItemResult<T>> resultTask) | ||
{ | ||
var result = await resultTask; | ||
return result.AssertSuccess(); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was successful. | ||
/// </summary> | ||
public static async Task AssertSuccess(this Task<ItemResult> resultTask) | ||
{ | ||
var result = await resultTask; | ||
Assert.True(result.WasSuccessful); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the result was successful. | ||
/// </summary> | ||
/// <param name="expectedValue">Expected value on the result.</param> | ||
public static void AssertSuccess<T>(this ItemResult<T> result, T expectedValue) | ||
{ | ||
result.AssertSuccess(); | ||
Assert.Equal(expectedValue, result.Object); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
content/Coalesce.Starter.Vue.Data.Test/Utilities/SqliteDatabaseFixture.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using Microsoft.Data.Sqlite; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Coalesce.Starter.Vue.Data.Test; | ||
|
||
public class SqliteDatabaseFixture : IDisposable | ||
{ | ||
private readonly SqliteConnection _HoldOpenConnection; | ||
|
||
public DbContextOptions<AppDbContext> Options { get; } | ||
|
||
private static readonly ILoggerFactory _LoggerFac = LoggerFactory.Create(b => | ||
{ | ||
b.SetMinimumLevel(LogLevel.Error); | ||
b.AddConsole(); | ||
}); | ||
|
||
public SqliteDatabaseFixture() | ||
{ | ||
// Use a unique database instance per test. | ||
var connString = $"Data Source=A{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; | ||
|
||
// Per https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/in-memory-databases#shareable-in-memory-databases, | ||
// a connection must be kept open in order to preserve a particular in-memory SQLite instance. | ||
// EF doesn't hold connections open necessarily, so we'll do this ourselves. | ||
_HoldOpenConnection = new SqliteConnection(connString); | ||
_HoldOpenConnection.Open(); | ||
|
||
var dbOptionBuilder = new DbContextOptionsBuilder<AppDbContext>(); | ||
dbOptionBuilder.UseSqlite(connString); | ||
dbOptionBuilder.UseLoggerFactory(_LoggerFac); | ||
dbOptionBuilder.EnableDetailedErrors(true); | ||
dbOptionBuilder.EnableSensitiveDataLogging(true); | ||
|
||
Options = dbOptionBuilder.Options; | ||
using var db = new AppDbContextForSqlite(Options); | ||
|
||
db.Database.EnsureCreated(); | ||
Seed(); | ||
} | ||
|
||
public void Seed() | ||
{ | ||
// Seed baseline test data, if desired. | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_HoldOpenConnection.Dispose(); | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
content/Coalesce.Starter.Vue.Data.Test/Utilities/TestBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
using IntelliTect.Coalesce; | ||
using IntelliTect.Coalesce.TypeDefinition; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.Extensions.Caching.Memory; | ||
using Moq; | ||
using Moq.AutoMock; | ||
using System.Security.Claims; | ||
|
||
namespace Coalesce.Starter.Vue.Data.Test; | ||
|
||
public class TestBase : IDisposable | ||
{ | ||
protected SqliteDatabaseFixture DbFixture { get; } | ||
|
||
private MockerScope? _CurrentMocker; | ||
|
||
protected AutoMocker Mocker => _CurrentMocker ?? throw new InvalidOperationException("The current mocker has been disposed."); | ||
|
||
protected AppDbContext Db => Mocker.Get<AppDbContext>(); | ||
|
||
protected ClaimsPrincipal CurrentUser { get; set; } = new(); | ||
|
||
public TestBase() | ||
{ | ||
ReflectionRepository.Global.AddAssembly<AppDbContext>(); | ||
|
||
DbFixture = new SqliteDatabaseFixture(); | ||
|
||
_CurrentMocker = BeginMockScope(standalone: false); | ||
} | ||
|
||
public class MockerScope : AutoMocker, IDisposable | ||
{ | ||
private readonly TestBase? _Parent; | ||
|
||
public MockerScope(TestBase? parent) : base(MockBehavior.Loose) | ||
{ | ||
_Parent = parent; | ||
if (parent != null) parent._CurrentMocker = this; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
if (_Parent == null) return; | ||
Interlocked.CompareExchange(ref _Parent._CurrentMocker, null, this); | ||
this.AsDisposable().Dispose(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// <para> | ||
/// Create a new <see cref="Mocker"/>, allowing for new instances of all services | ||
/// to be obtained - especially a new <see cref="AppDbContext"/>. | ||
/// Persistence mechanisms are maintained, including the same <see cref="SqliteDatabaseFixture"/>. | ||
/// </para> | ||
/// <para> | ||
/// Doing this allows you to verify that test setup steps haven't polluted the state | ||
/// of services in such a way that will cause the test to behave differently than it would | ||
/// in a real scenario. | ||
/// </para> | ||
/// <para> | ||
/// For example, you should call this after setting up test data, and also in multi-step tests | ||
/// where the steps would normally be performed by separate web requests/background jobs/etc. | ||
/// </para> | ||
/// </summary> | ||
protected void RefreshServices() | ||
{ | ||
_CurrentMocker?.Dispose(); | ||
BeginMockScope(standalone: false); | ||
} | ||
|
||
/// <summary> | ||
/// Create an standalone mocker instance that can be used to acquire services. | ||
/// Usually you should use the current mock scope in <see cref="Mocker"/>, | ||
/// resetting it with <see cref="RefreshServices"/> as needed. | ||
/// Only create an independent scope for operations like parallel processing | ||
/// (an unusual thing to be doing in unit tests). | ||
/// </summary> | ||
protected MockerScope BeginMockScope() => BeginMockScope(true); | ||
|
||
private MockerScope BeginMockScope(bool standalone = false) | ||
{ | ||
var mocker = new MockerScope(standalone ? null : this); | ||
var db = new AppDbContextForSqlite(DbFixture.Options); | ||
mocker.Use(DbFixture.Options); | ||
mocker.Use<AppDbContext>(db); | ||
|
||
mocker.Use<CrudContext<AppDbContext>>(new CrudContext<AppDbContext>( | ||
db, | ||
() => CurrentUser | ||
)); | ||
|
||
mocker.GetMock<IDbContextFactory<AppDbContext>>() | ||
.Setup(x => x.CreateDbContextAsync(It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(() => new AppDbContextForSqlite(DbFixture.Options)); | ||
|
||
mocker.GetMock<IDbContextFactory<AppDbContext>>() | ||
.Setup(x => x.CreateDbContext()) | ||
.Returns(() => new AppDbContextForSqlite(DbFixture.Options)); | ||
|
||
mocker.Use<IMemoryCache>(new MemoryCache(new MemoryCacheOptions())); | ||
|
||
// Register additional services required by tests, | ||
// preferring real implementations where possible | ||
// in order to improve test fidelity. | ||
|
||
return mocker; | ||
} | ||
|
||
public virtual void Dispose() | ||
{ | ||
_CurrentMocker?.Dispose(); | ||
DbFixture.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,5 +16,4 @@ public enum WidgetCategory | |
Whizbangs, | ||
Sprecklesprockets, | ||
Discombobulators, | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.