mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-29 18:21:08 +00:00
Add first command and initial tests
This commit is contained in:
parent
cf71754fb0
commit
7b31d2dd1b
@ -21,9 +21,10 @@ public class UserController : ApiController
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public string GetAllUsersAsync()
|
||||
public async Task<IActionResult> GetAllUsersAsync()
|
||||
{
|
||||
return "test";
|
||||
var users = await _userService.GetAllUsersAsync();
|
||||
return Response(users);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
|
@ -1,4 +1,5 @@
|
||||
using CleanArchitecture.Application.Extensions;
|
||||
using CleanArchitecture.Domain.Extensions;
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using CleanArchitecture.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -24,6 +25,8 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
builder.Services.AddInfrastructure();
|
||||
builder.Services.AddQueryHandlers();
|
||||
builder.Services.AddServices();
|
||||
builder.Services.AddCommandHandlers();
|
||||
builder.Services.AddNotificationHandlers();
|
||||
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
{
|
||||
|
@ -9,8 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -21,4 +22,9 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -0,0 +1,33 @@
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Application.Tests.Fixtures.Queries;
|
||||
|
||||
public class QueryHandlerBaseFixture
|
||||
{
|
||||
public Mock<IMediatorHandler> Bus { get; } = new();
|
||||
|
||||
public QueryHandlerBaseFixture VerifyExistingNotification(string key, string errorCode, string message)
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(
|
||||
It.Is<DomainNotification>(
|
||||
notification =>
|
||||
notification.Key == key &&
|
||||
notification.Code == errorCode &&
|
||||
notification.Value == message)),
|
||||
Times.Once);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryHandlerBaseFixture VerifyNoDomainNotification()
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(It.IsAny<DomainNotification>()),
|
||||
Times.Never);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Users;
|
||||
|
||||
public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture
|
||||
{
|
||||
public Mock<IUserRepository> UserRepository { get; }
|
||||
public GetAllUsersQueryHandler Handler { get; }
|
||||
|
||||
public GetAllUsersTestFixture()
|
||||
{
|
||||
UserRepository = new();
|
||||
|
||||
Handler = new(UserRepository.Object);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Users;
|
||||
|
||||
public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture
|
||||
{
|
||||
public Mock<IUserRepository> UserRepository { get; }
|
||||
public GetUserByIdQueryHandler Handler { get; }
|
||||
|
||||
public GetUserByIdTestFixture()
|
||||
{
|
||||
UserRepository = new();
|
||||
|
||||
Handler = new(UserRepository.Object, Bus.Object);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.Tests.Fixtures.Services;
|
||||
|
||||
public sealed class UserServiceTestFixture
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.Tests.Queries.Users;
|
||||
|
||||
public sealed class GetAllUsersQueryHandlerTests
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.Tests.Queries.Users;
|
||||
|
||||
public sealed class GetUserByIdQueryHandlerTests
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.Tests.Services;
|
||||
|
||||
public sealed class UserServiceTests
|
||||
{
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace CleanArchitecture.Application.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
global using Xunit;
|
@ -7,6 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Application.Services;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
@ -19,6 +21,7 @@ public static class ServiceCollectionExtension
|
||||
public static IServiceCollection AddQueryHandlers(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserViewModel?>, GetUserByIdQueryHandler>();
|
||||
services.AddScoped<IRequestHandler<GetAllUsersQuery, IEnumerable<UserViewModel>>, GetAllUsersQueryHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CleanArchitecture.Application.Interfaces;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
public Task<UserViewModel?> GetUserByUserIdAsync(Guid userId);
|
||||
public Task<IEnumerable<UserViewModel>> GetAllUsersAsync();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
|
||||
public sealed record GetAllUsersQuery() : IRequest<IEnumerable<UserViewModel>>;
|
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
|
||||
public sealed class GetAllUsersQueryHandler :
|
||||
IRequestHandler<GetAllUsersQuery, IEnumerable<UserViewModel>>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public GetAllUsersQueryHandler(IUserRepository userRepository)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<UserViewModel>> Handle(GetAllUsersQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _userRepository
|
||||
.GetAllNoTracking()
|
||||
.Select(x => UserViewModel.FromUser(x))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
@ -20,4 +22,9 @@ public sealed class UserService : IUserService
|
||||
{
|
||||
return await _bus.QueryAsync(new GetUserByIdQuery(userId));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<UserViewModel>> GetAllUsersAsync()
|
||||
{
|
||||
return await _bus.QueryAsync(new GetAllUsersQuery());
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
@ -10,4 +10,9 @@
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Commands\Users\CreateUser" />
|
||||
<Folder Include="Commands\Users\UpdateUser" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
82
CleanArchitecture.Domain/Commands/CommandHandlerBase.cs
Normal file
82
CleanArchitecture.Domain/Commands/CommandHandlerBase.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands;
|
||||
|
||||
public abstract class CommandHandlerBase
|
||||
{
|
||||
protected readonly IMediatorHandler _bus;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly DomainNotificationHandler _notifications;
|
||||
|
||||
protected CommandHandlerBase(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications)
|
||||
{
|
||||
_bus = bus;
|
||||
_unitOfWork = unitOfWork;
|
||||
_notifications = (DomainNotificationHandler)notifications;
|
||||
}
|
||||
|
||||
public async Task<bool> CommitAsync()
|
||||
{
|
||||
if (_notifications.HasNotifications())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
"Commit",
|
||||
"Problem occured while saving the data. Please try again.",
|
||||
ErrorCodes.CommitFailed));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async Task NotifyAsync(string key, string message, string code)
|
||||
{
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(key, message, code));
|
||||
}
|
||||
|
||||
protected async Task NotifyAsync(DomainNotification notification)
|
||||
{
|
||||
await _bus.RaiseEventAsync(notification);
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> TestValidityAsync(CommandBase command)
|
||||
{
|
||||
if (command.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command.ValidationResult == null)
|
||||
{
|
||||
throw new InvalidOperationException("Command is invalid and should therefore have a validation result");
|
||||
}
|
||||
|
||||
foreach (var error in command.ValidationResult!.Errors)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
command.MessageType,
|
||||
error.ErrorMessage,
|
||||
error.ErrorCode,
|
||||
error.FormattedMessagePlaceholderValues));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommand : CommandBase
|
||||
{
|
||||
private static readonly DeleteUserCommandValidation _validation = new();
|
||||
|
||||
public Guid UserId { get; }
|
||||
|
||||
public DeleteUserCommand(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<DeleteUserCommand>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public DeleteUserCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
IUserRepository userRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(DeleteUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Id {request.UserId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_userRepository.Remove(user);
|
||||
|
||||
if (!await CommitAsync())
|
||||
{
|
||||
await _bus.RaiseEventAsync(new UserDeletedEvent(request.UserId));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommandValidation : AbstractValidator<DeleteUserCommand>
|
||||
{
|
||||
public DeleteUserCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
}
|
6
CleanArchitecture.Domain/Errors/DomainErrorCodes.cs
Normal file
6
CleanArchitecture.Domain/Errors/DomainErrorCodes.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Domain.Errors;
|
||||
|
||||
public static class DomainErrorCodes
|
||||
{
|
||||
public const string UserEmptyId = "USER_EMPTY_ID";
|
||||
}
|
15
CleanArchitecture.Domain/EventHandler/UserEventHandler.cs
Normal file
15
CleanArchitecture.Domain/EventHandler/UserEventHandler.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.EventHandler;
|
||||
|
||||
public sealed class UserEventHandler :
|
||||
INotificationHandler<UserDeletedEvent>
|
||||
{
|
||||
public Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
13
CleanArchitecture.Domain/Events/User/UserDeletedEvent.cs
Normal file
13
CleanArchitecture.Domain/Events/User/UserDeletedEvent.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.User;
|
||||
|
||||
public sealed class UserDeletedEvent : DomainEvent
|
||||
{
|
||||
public Guid UserId { get; }
|
||||
|
||||
public UserDeletedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.EventHandler;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CleanArchitecture.Domain.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddCommandHandlers(this IServiceCollection services)
|
||||
{
|
||||
// User
|
||||
services.AddScoped<IRequestHandler<DeleteUserCommand>, DeleteUserCommandHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddNotificationHandlers(this IServiceCollection services)
|
||||
{
|
||||
// User
|
||||
services.AddScoped<INotificationHandler<UserDeletedEvent>, UserEventHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -21,4 +21,5 @@ public interface IRepository<TEntity> : IDisposable where TEntity : Entity
|
||||
void Update(TEntity entity);
|
||||
|
||||
Task<bool> ExistsAsync(Guid id);
|
||||
public void Remove(TEntity entity, bool hardDelete = false);
|
||||
}
|
@ -9,8 +9,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="FluentAssertions" Version="6.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -21,4 +23,8 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -0,0 +1,16 @@
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Tests.Fixtures;
|
||||
|
||||
public static class UnitOfWorkTestFixture
|
||||
{
|
||||
public static UnitOfWork<ApplicationDbContext> GetUnitOfWork(
|
||||
ApplicationDbContext dbContext,
|
||||
ILogger<UnitOfWork<ApplicationDbContext>> logger)
|
||||
{
|
||||
var unitOfWork = new UnitOfWork<ApplicationDbContext>(dbContext, logger);
|
||||
|
||||
return unitOfWork;
|
||||
}
|
||||
}
|
57
CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs
Normal file
57
CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Tests;
|
||||
|
||||
public sealed class InMemoryBusTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task InMemoryBus_Should_Publish_When_Not_DomainNotification()
|
||||
{
|
||||
var mediator = new Mock<IMediator>();
|
||||
|
||||
var inMemoryBus = new InMemoryBus(mediator.Object);
|
||||
|
||||
var key = "Key";
|
||||
var value = "Value";
|
||||
var code = "Code";
|
||||
|
||||
var domainEvent = new DomainNotification(key, value, code);
|
||||
|
||||
await inMemoryBus.RaiseEventAsync(domainEvent);
|
||||
|
||||
mediator.Verify(x => x.Publish(domainEvent, CancellationToken.None), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InMemoryBus_Should_Save_And_Publish_When_DomainNotification()
|
||||
{
|
||||
var mediator = new Mock<IMediator>();
|
||||
|
||||
var inMemoryBus = new InMemoryBus(mediator.Object);
|
||||
|
||||
var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid());
|
||||
|
||||
await inMemoryBus.RaiseEventAsync(userDeletedEvent);
|
||||
|
||||
mediator.Verify(x => x.Publish(userDeletedEvent, CancellationToken.None), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InMemoryBus_Should_Send_Command_Async()
|
||||
{
|
||||
var mediator = new Mock<IMediator>();
|
||||
|
||||
var inMemoryBus = new InMemoryBus(mediator.Object);
|
||||
|
||||
var deleteUserCommand = new DeleteUserCommand(Guid.NewGuid());
|
||||
|
||||
await inMemoryBus.SendCommandAsync(deleteUserCommand);
|
||||
|
||||
mediator.Verify(x => x.Send(deleteUserCommand, CancellationToken.None), Times.Once);
|
||||
}
|
||||
}
|
66
CleanArchitecture.Infrastructure.Tests/UnitOfWorkTests.cs
Normal file
66
CleanArchitecture.Infrastructure.Tests/UnitOfWorkTests.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using CleanArchitecture.Infrastructure.Tests.Fixtures;
|
||||
using FluentAssertions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Tests;
|
||||
|
||||
public sealed class UnitOfWorkTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Should_Commit_Async_Returns_True()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
var dbContextMock = new Mock<ApplicationDbContext>(options.Options);
|
||||
var loggerMock = new Mock<ILogger<UnitOfWork<ApplicationDbContext>>>();
|
||||
|
||||
dbContextMock
|
||||
.Setup(x => x.SaveChangesAsync(CancellationToken.None))
|
||||
.Returns(Task.FromResult(1));
|
||||
|
||||
var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object);
|
||||
|
||||
var result = await unitOfWork.CommitAsync();
|
||||
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Commit_Async_Returns_False()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
var dbContextMock = new Mock<ApplicationDbContext>(options.Options);
|
||||
var loggerMock = new Mock<ILogger<UnitOfWork<ApplicationDbContext>>>();
|
||||
|
||||
dbContextMock
|
||||
.Setup(x => x.SaveChangesAsync(CancellationToken.None))
|
||||
.Throws(new DbUpdateException("Boom", new System.Exception("it broke")));
|
||||
|
||||
var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object);
|
||||
|
||||
var result = await unitOfWork.CommitAsync();
|
||||
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Throw_Exception_When_Commiting_With_DbUpdateException()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
var dbContextMock = new Mock<ApplicationDbContext>(options.Options);
|
||||
var loggerMock = new Mock<ILogger<UnitOfWork<ApplicationDbContext>>>();
|
||||
|
||||
dbContextMock
|
||||
.Setup(x => x.SaveChangesAsync(CancellationToken.None))
|
||||
.Throws(new Exception("boom"));
|
||||
|
||||
var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object);
|
||||
|
||||
Func<Task> knalltAction = async () => await unitOfWork.CommitAsync();
|
||||
|
||||
await knalltAction.Should().ThrowAsync<Exception>();
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace CleanArchitecture.Infrastructure.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
global using Xunit;
|
@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
public sealed class ApplicationDbContext : DbContext
|
||||
public class ApplicationDbContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@ -73,4 +72,18 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
|
||||
{
|
||||
return _dbSet.AnyAsync(entity => entity.Id == id);
|
||||
}
|
||||
|
||||
public void Remove(TEntity entity, bool hardDelete = false)
|
||||
{
|
||||
if (hardDelete)
|
||||
{
|
||||
_dbSet.Remove(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Delete();
|
||||
_dbSet.Update(entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user