Added unit testing with xunit

master
Tudor Stanciu 2023-03-10 20:11:11 +02:00
parent 919ab053bc
commit 3f0412e7c5
9 changed files with 281 additions and 3 deletions

View File

@ -8,6 +8,7 @@ namespace Tuitio.Domain.Data.DbContexts
{ {
public class TuitioDbContext : DbContext public class TuitioDbContext : DbContext
{ {
public DbSet<UserStatus> UserStatuses{ get; set; }
public DbSet<AppUser> Users { get; set; } public DbSet<AppUser> Users { get; set; }
public DbSet<UserToken> UserTokens { get; set; } public DbSet<UserToken> UserTokens { get; set; }

View File

@ -27,8 +27,7 @@ namespace Tuitio.Extensions
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
// AutoMapper // AutoMapper
services.AddAutoMapper( services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
typeof(Application.Mappings.MappingProfile).Assembly);
// Swagger // Swagger
services.AddSwagger("Tuitio API", AuthorizationType.None); services.AddSwagger("Tuitio API", AuthorizationType.None);

View File

@ -0,0 +1,120 @@
using MediatR;
using MediatR.Pipeline;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Data.Common;
using Tuitio.Domain.Data.DbContexts;
using Xunit;
namespace Tuitio.Application.Tests.Fixtures
{
public class DependencyInjectionFixture : IAsyncLifetime
{
private readonly Guid _prefix;
private readonly List<SqliteConnection> _connections;
public readonly IServiceProvider ServiceProvider;
public DependencyInjectionFixture()
{
_prefix = Guid.NewGuid();
_connections = new List<SqliteConnection>();
ServiceProvider = BuildServiceProvider();
}
public async Task DisposeAsync()
{
foreach (var con in _connections)
await con.DisposeAsync();
}
public async Task InitializeAsync()
{
await PrepareDbAsync();
SeedData();
}
private IServiceProvider BuildServiceProvider()
{
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.tuitio.json", optional: true, reloadOnChange: false);
var configuration = configurationBuilder.Build();
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddLogging(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning);
builder.AddFilter("Microsoft.*", LogLevel.Warning);
});
// MediatR
services.AddMediatR(typeof(Application.CommandHandlers.AccountLoginHandler).Assembly);
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
// AutoMapper
services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
services.AddDbContext<TuitioDbContext>(options =>
{
options.UseSqlite(GetOrAddInMemoryDatabase("TuitioDbContext"));
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
});
services.AddApplicationServices();
var provider = services.BuildServiceProvider();
return provider;
}
private DbConnection GetOrAddInMemoryDatabase(string code)
{
var connection = new SqliteConnection($"Data Source={_prefix}{code};Mode=Memory;Cache=shared");
_connections.Add(connection);
connection.Open();
return connection;
}
private async Task PrepareDbAsync()
{
var result = await ServiceProvider.GetRequiredService<TuitioDbContext>().Database.EnsureCreatedAsync();
if (!result) throw new Exception("TuitioDbContext exists");
var script = await File.ReadAllTextAsync("seed.sql");
await ServiceProvider.GetRequiredService<TuitioDbContext>().Database.ExecuteSqlRawAsync(script);
}
private void SeedData()
{
using var dbContext = ServiceProvider.GetRequiredService<TuitioDbContext>();
dbContext.UserStatuses.AddRange(new Domain.Entities.UserStatus[]
{
new Domain.Entities.UserStatus()
{
StatusId = 1,
StatusCode = "ACTIVE",
StatusName = "Active"
},
new Domain.Entities.UserStatus()
{
StatusId = 2,
StatusCode = "INACTIVE",
StatusName = "Inactive"
},
new Domain.Entities.UserStatus()
{
StatusId = 3,
StatusCode = "BLOCKED",
StatusName = "Blocked"
}
});
dbContext.SaveChanges();
}
}
}

View File

@ -0,0 +1,22 @@
using Tuitio.Application.Services;
using Xunit;
namespace Tuitio.Application.Tests
{
public class HashingServiceTests
{
[Fact]
public void HashSha256_ShouldReturnCorrectHash()
{
// Arrange
var expected = "9B8769A4A742959A2D0298C36FB70623F2DFACDA8436237DF08D8DFD5B37374C";
var hashingService = new HashingService();
// Act
var actual = hashingService.HashSha256("pass123");
// Assert
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Tests.Fixtures;
using Tuitio.Domain.Data.DbContexts;
using Xunit;
namespace Tuitio.Application.Tests
{
public class LocalSqliteDatabaseTests : IClassFixture<DependencyInjectionFixture>, IDisposable
{
private readonly IServiceScope _tuitioScope;
public LocalSqliteDatabaseTests(DependencyInjectionFixture fixture)
{
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}
public void Dispose()
{
_tuitioScope.Dispose();
}
[Fact]
public async Task EnsureExpectedDataInLocalDbTest()
{
// Arrange
var _dbContext = _tuitioScope.ServiceProvider.GetRequiredService<TuitioDbContext>();
// Act
var statuses = await _dbContext.UserStatuses.ToArrayAsync();
// Assert
Assert.NotEmpty(statuses);
Assert.True(statuses.All(z => !string.IsNullOrWhiteSpace(z.StatusCode)));
Assert.Contains(statuses, z => z.StatusCode == "ACTIVE");
}
}
}

View File

@ -0,0 +1,64 @@
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Services.Abstractions;
using Tuitio.Application.Tests.Fixtures;
using Tuitio.Domain.Entities;
using Xunit;
namespace Tuitio.Application.Tests
{
public class TokenServiceTests : IClassFixture<DependencyInjectionFixture>, IDisposable
{
private readonly IServiceScope _tuitioScope;
private readonly AppUser _userMock;
public TokenServiceTests(DependencyInjectionFixture fixture)
{
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
_userMock = MockAppUser();
}
private AppUser MockAppUser()
{
var user = new AppUser()
{
UserId = 0,
UserName = "tuitio.test",
Password = "9B8769A4A742959A2D0298C36FB70623F2DFACDA8436237DF08D8DFD5B37374C", //pass123
Email = "tuitio.test@test.test",
FirstName = "tuitio",
LastName = "test",
StatusId = 1,
FailedLoginAttempts = 0,
SecurityStamp = "A93650FF-1FC4-4999-BAB6-3EEB174F6892",
CreationDate = DateTime.Now
};
return user;
}
public void Dispose()
{
_tuitioScope.Dispose();
}
[Fact]
public void TokenGenerationTest()
{
// Arrange
var _tokenService = _tuitioScope.ServiceProvider.GetRequiredService<ITokenService>();
// Act
var token = _tokenService.GenerateToken(_userMock);
var raw = _tokenService.GenerateTokenRaw(token);
var extracted = _tokenService.ExtractToken(raw);
// Assert
Assert.NotNull(token);
Assert.NotNull(raw);
Assert.NotNull(extracted);
Assert.True(_userMock.UserName == extracted.UserName);
Assert.True(_userMock.SecurityStamp == extracted.SecurityStamp);
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
@ -7,7 +7,23 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="appsettings.tuitio.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.tuitio.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.14" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -17,6 +33,13 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Tuitio.Application\Tuitio.Application.csproj" /> <ProjectReference Include="..\..\..\src\Tuitio.Application\Tuitio.Application.csproj" />
<ProjectReference Include="..\..\..\src\Tuitio.Domain.Data\Tuitio.Domain.Data.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="seed.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,8 @@
{
"Restrictions": {
"MaxFailedLoginAttempts": 5
},
"Token": {
"ValidityInMinutes": 43800
}
}

View File

@ -0,0 +1,3 @@

SELECT 'empty'