nice refactorings

This commit is contained in:
2022-10-10 03:17:14 +03:00
parent 4ca234aec8
commit 28a6981001
30 changed files with 622 additions and 141 deletions
@@ -0,0 +1,14 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace PracticeCalendar.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ApiControllerBase : ControllerBase
{
private ISender _mediator = null!;
protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService<ISender>();
}
}
@@ -1,111 +1,61 @@
using AutoMapper;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PracticeCalendar.API.Model; using PracticeCalendar.API.Controllers;
using PracticeCalendar.Domain.Common.Interfaces; using PracticeCalendar.Application.PracticeEvents.Commands;
using PracticeCalendar.Domain.Entities; using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Entities.Specifications; using PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents;
namespace PrcaticeCalendar.Controllers namespace PrcaticeCalendar.Controllers
{ {
[ApiController] public class EventsController : ApiControllerBase
[Route("api/[controller]")]
public class EventsController : ControllerBase
{ {
private readonly IRepository<PracticeEvent> eventsRepo;
private readonly IMapper mapper;
private readonly ILogger<EventsController> logger; private readonly ILogger<EventsController> logger;
public EventsController(IRepository<PracticeEvent> eventsRepo, public EventsController(ILogger<EventsController> logger)
IMapper mapper,
ILogger<EventsController> logger)
{ {
this.eventsRepo = eventsRepo;
this.mapper = mapper;
this.logger = logger; this.logger = logger;
} }
[HttpGet(Name = "GetAll")] [HttpGet(Name = "GetAll")]
public async Task<ActionResult<List<EventModel>>> Get() public async Task<ActionResult<List<PracticeEventDto>>> Get()
{ {
return await Mediator.Send(new GetPracticeEventsQuery());
var spec = new PracticeEventsWithAttendees();
var repoList = await eventsRepo.ListAsync(spec);
var evList = repoList.Select(x=> {
var model = mapper.Map<EventModel>(x);
model.Attendees = x.Attendees.Select(m=>mapper.Map<AttendeeModel>(m)).ToArray();
return model;
})
.ToList();
return evList;
} }
[HttpPost(Name = "Create practice event")] [HttpPost(Name = "Create practice event")]
public async Task<IActionResult> CreateEvent(EventModel eventModel) public async Task<ActionResult<PracticeEventDto>> CreateEvent(PracticeEventDto eventModel)
{ {
var practiceEvent = new PracticeEvent(eventModel.Title, eventModel.Description); return await Mediator.Send(new CreatePracticeEventCommand(eventModel));
foreach (var att in eventModel.Attendees)
{
practiceEvent.AddAttendee(new Attendee(att.Name, att.EmailAddress));
}
var result = await eventsRepo.AddAsync(practiceEvent);
await eventsRepo.SaveChangesAsync();
return Ok(mapper.Map<EventModel>(result));
} }
[HttpPut(Name = "Update practice event")] [HttpPut(Name = "Update practice event")]
public async Task<IActionResult> UpdateEvent(EventModel eventModel) public async Task<ActionResult<PracticeEventDto>> UpdateEvent(PracticeEventDto eventModel)
{ {
var practiceEvent = await eventsRepo.GetByIdAsync(eventModel.Id); return await Mediator.Send(new UpdatePracticeEventCommand(eventModel));
if(practiceEvent == null)
{
return NotFound();
}
practiceEvent.UpdateTitleAndDescription(eventModel.Title, eventModel.Description);
await eventsRepo.UpdateAsync(practiceEvent);
await eventsRepo.SaveChangesAsync();
return Ok(mapper.Map<EventModel>(practiceEvent));
} }
[HttpDelete(Name = "Delete practice event")] [HttpDelete(Name = "Delete practice event")]
public async Task<IActionResult> DeleteEvent(int practiceEventId) public async Task<ActionResult> DeleteEvent(int practiceEventId)
{ {
var org = await eventsRepo.GetByIdAsync(practiceEventId); await Mediator.Send(new DeletePracticeEventCommand(practiceEventId));
if (org == null)
{
return NotFound();
}
await eventsRepo.DeleteAsync(org);
await eventsRepo.SaveChangesAsync();
return Ok(); return Ok();
} }
[HttpPost] [HttpPost]
[Route("accept/{eventId}/{attendeeId}")] [Route("accept/{eventId}/{attendeeId}")]
public async Task<IActionResult> AttendeeAcceptEvent(int eventId, int attendeeId) public async Task<ActionResult> AttendeeAcceptEvent(int eventId, int attendeeId)
{ {
var spec = new PracticeEventByIdWithAttendees(eventId); await Mediator.Send(new AttendeeAcceptEventCommand(eventId, attendeeId));
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec);
if (practiceEvent == null)
{
return NotFound();
}
practiceEvent.AttendeeAcceptEvent(attendeeId);
await eventsRepo.SaveChangesAsync();
return Ok(); return Ok();
} }
[HttpPost] [HttpPost]
[Route("decline/{eventId}/{attendeeId}")] [Route("decline/{eventId}/{attendeeId}")]
public async Task<IActionResult> AttendeeDeclineEvent(int eventId, int attendeeId) public async Task<ActionResult> AttendeeDeclineEvent(int eventId, int attendeeId)
{ {
var spec = new PracticeEventByIdWithAttendees(eventId); await Mediator.Send(new AttendeeDeclineEventCommand(eventId, attendeeId));
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec);
if (practiceEvent == null)
{
return NotFound();
}
practiceEvent.AttendeeDeclineEvent(attendeeId);
await eventsRepo.SaveChangesAsync();
return Ok(); return Ok();
} }
} }
-34
View File
@@ -1,34 +0,0 @@
using AutoMapper;
using PracticeCalendar.Domain.Entities;
namespace PracticeCalendar.API.Model
{
public class EventModel
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public AttendeeModel[] Attendees { get; set; } = Array.Empty<AttendeeModel>();
}
public class AttendeeModel
{
public string Name { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public bool IsAttending { get; set; }
}
public class EventModelProfile : Profile
{
public EventModelProfile()
{
CreateMap<PracticeEvent, EventModel>();
CreateMap<EventModel, PracticeEvent>();
CreateMap<Attendee, AttendeeModel>();
}
}
}
@@ -9,8 +9,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" /> <PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
<PackageReference Include="Mapster.Core" Version="1.2.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup> </ItemGroup>
@@ -19,4 +19,8 @@
<ProjectReference Include="..\PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj" /> <ProjectReference Include="..\PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Model\" />
</ItemGroup>
</Project> </Project>
+7 -2
View File
@@ -1,5 +1,6 @@
using Hellang.Middleware.ProblemDetails; using Hellang.Middleware.ProblemDetails;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using PracticeCalendar.Application;
using PracticeCalendar.Infrastructure; using PracticeCalendar.Infrastructure;
using PracticeCalendar.Infrastructure.Persistence; using PracticeCalendar.Infrastructure.Persistence;
using System; using System;
@@ -16,14 +17,18 @@ namespace PracticeCalendar
builder.Services.AddProblemDetails(); builder.Services.AddProblemDetails();
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddControllers(); builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
//inject application
builder.Services.AddApplicationServices();
//inject infrastructure //inject infrastructure
builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddInfrastructureServices(builder.Configuration);
//mapster
var app = builder.Build(); var app = builder.Build();
Binary file not shown.
@@ -0,0 +1,18 @@
using MapsterMapper;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace PracticeCalendar.Application
{
public static class ConfigureServices
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddSingleton<IMapper>(new Mapper(new Mapster.TypeAdapterConfig()));
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;
}
}
}
@@ -11,11 +11,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="PracticeEvents\Commands\" /> <PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
</ItemGroup> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,44 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.Specifications;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
public record AttendeeAcceptEventCommand : IRequest
{
public AttendeeAcceptEventCommand(int eventId, int attendeeId)
{
EventId = eventId;
AttendeeId = attendeeId;
}
public int EventId { get; }
public int AttendeeId { get; }
}
public class AttendeeAcceptEventCommandHandler : IRequestHandler<AttendeeAcceptEventCommand>
{
private readonly IRepository<PracticeEvent> eventsRepo;
public AttendeeAcceptEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
{
this.eventsRepo = eventsRepo;
}
public async Task<Unit> Handle(AttendeeAcceptEventCommand request, CancellationToken cancellationToken)
{
var spec = new PracticeEventByIdWithAttendees(request.EventId);
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
if (practiceEvent == null)
{
throw new PracticeEventNotFoundException();
}
practiceEvent.AttendeeAcceptEvent(request.AttendeeId);
await eventsRepo.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
}
@@ -0,0 +1,43 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.Specifications;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
public record AttendeeDeclineEventCommand : IRequest
{
public AttendeeDeclineEventCommand(int eventId, int attendeeId)
{
EventId = eventId;
AttendeeId = attendeeId;
}
public int EventId { get; }
public int AttendeeId { get; }
}
public class AttendeeDeclineEventCommandHandler : IRequestHandler<AttendeeDeclineEventCommand>
{
private readonly IRepository<PracticeEvent> eventsRepo;
public AttendeeDeclineEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
{
this.eventsRepo = eventsRepo;
}
public async Task<Unit> Handle(AttendeeDeclineEventCommand request, CancellationToken cancellationToken)
{
var spec = new PracticeEventByIdWithAttendees(request.EventId);
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
if (practiceEvent == null)
{
throw new PracticeEventNotFoundException();
}
practiceEvent.AttendeeDeclineEvent(request.AttendeeId);
return Unit.Value;
}
}
}
@@ -0,0 +1,41 @@
using Mapster;
using MediatR;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
public record CreatePracticeEventCommand : IRequest<PracticeEventDto>
{
public CreatePracticeEventCommand(PracticeEventDto eventDto)
{
Event = eventDto;
}
public PracticeEventDto Event { get; }
}
public class CreatePracticeEventCommandHandler : IRequestHandler<CreatePracticeEventCommand, PracticeEventDto>
{
private readonly IRepository<PracticeEvent> eventsRepo;
public CreatePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
{
this.eventsRepo = eventsRepo;
}
public async Task<PracticeEventDto> Handle(CreatePracticeEventCommand request, CancellationToken cancellationToken)
{
var input = request.Event;
var practiceEvent = new PracticeEvent(input.Title, input.Description);
foreach (var att in input.Attendees)
{
practiceEvent.AddAttendee(new Attendee(att.Name, att.EmailAddress));
}
var result = await eventsRepo.AddAsync(practiceEvent, cancellationToken);
await eventsRepo.SaveChangesAsync(cancellationToken);
return result.Adapt<PracticeEventDto>();
}
}
}
@@ -0,0 +1,39 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
public record DeletePracticeEventCommand : IRequest
{
public DeletePracticeEventCommand(int practiceEventId)
{
PracticeEventId = practiceEventId;
}
public int PracticeEventId { get; }
}
public class DeletePracticeEventCommandHandler : IRequestHandler<DeletePracticeEventCommand>
{
private readonly IRepository<PracticeEvent> eventsRepo;
public DeletePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
{
this.eventsRepo = eventsRepo;
}
public async Task<Unit> Handle(DeletePracticeEventCommand request, CancellationToken cancellationToken)
{
var org = await eventsRepo.GetByIdAsync(request.PracticeEventId, cancellationToken);
if (org == null)
{
throw new PracticeEventNotFoundException();
}
await eventsRepo.DeleteAsync(org, cancellationToken);
await eventsRepo.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
}
@@ -0,0 +1,43 @@
using Mapster;
using MediatR;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
public record UpdatePracticeEventCommand : IRequest<PracticeEventDto>
{
public UpdatePracticeEventCommand(PracticeEventDto eventDto)
{
Event = eventDto;
}
public PracticeEventDto Event { get; }
}
public class UpdatePracticeEventCommandHandler : IRequestHandler<UpdatePracticeEventCommand, PracticeEventDto>
{
private readonly IRepository<PracticeEvent> eventsRepo;
public UpdatePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
{
this.eventsRepo = eventsRepo;
}
public async Task<PracticeEventDto> Handle(UpdatePracticeEventCommand request, CancellationToken cancellationToken)
{
var eventModel = request.Event;
var practiceEvent = await eventsRepo.GetByIdAsync(eventModel.Id, cancellationToken);
if (practiceEvent == null)
{
throw new PracticeEventNotFoundException();
}
practiceEvent.UpdateTitleAndDescription(eventModel.Title, eventModel.Description);
await eventsRepo.UpdateAsync(practiceEvent, cancellationToken);
await eventsRepo.SaveChangesAsync(cancellationToken);
return practiceEvent.Adapt<PracticeEventDto>();
}
}
}
@@ -0,0 +1,10 @@
namespace PracticeCalendar.Application.PracticeEvents.Queries
{
public class AttendeeDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public bool IsAttending { get; set; }
}
}
@@ -1,4 +1,6 @@
using MediatR; using Mapster;
using MapsterMapper;
using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PracticeCalendar.Domain.Common.Interfaces; using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities; using PracticeCalendar.Domain.Entities;
@@ -6,27 +8,31 @@ using PracticeCalendar.Domain.Entities.Specifications;
namespace PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents namespace PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents
{ {
public record class GetPracticeEventsQuery : IRequest<List<PracticeEvent>> public record class GetPracticeEventsQuery : IRequest<List<PracticeEventDto>>
{ {
} }
public class GetPracticeEventsQueryHandler : IRequestHandler<GetPracticeEventsQuery, List<PracticeEvent>> public class GetPracticeEventsQueryHandler : IRequestHandler<GetPracticeEventsQuery, List<PracticeEventDto>>
{ {
private readonly ILogger<GetPracticeEventsQueryHandler> logger; private readonly ILogger<GetPracticeEventsQueryHandler> logger;
private readonly IRepository<PracticeEvent> eventsRepo; private readonly IRepository<PracticeEvent> eventsRepo;
private readonly IMapper mapper;
public GetPracticeEventsQueryHandler(IRepository<PracticeEvent> eventsRepo, public GetPracticeEventsQueryHandler(IRepository<PracticeEvent> eventsRepo,
ILogger<GetPracticeEventsQueryHandler> logger) ILogger<GetPracticeEventsQueryHandler> logger,
IMapper mapper)
{ {
this.eventsRepo = eventsRepo; this.eventsRepo = eventsRepo;
this.logger = logger; this.logger = logger;
this.mapper = mapper;
} }
public async Task<List<PracticeEvent>> Handle(GetPracticeEventsQuery request, CancellationToken cancellationToken) public async Task<List<PracticeEventDto>> Handle(GetPracticeEventsQuery request, CancellationToken cancellationToken)
{ {
var spec = new PracticeEventsWithAttendees(); var spec = new PracticeEventsWithAttendees();
var evList = await eventsRepo.ListAsync(spec); var evList = await eventsRepo.ListAsync(spec, cancellationToken);
return evList; var lst = evList.Adapt<List<PracticeEventDto>>(mapper.Config);
return lst;
} }
} }
} }
@@ -0,0 +1,14 @@
namespace PracticeCalendar.Application.PracticeEvents.Queries
{
public class PracticeEventDto
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public IList<AttendeeDto> Attendees { get; set; } = new List<AttendeeDto>();
}
}
@@ -22,8 +22,7 @@ namespace PracticeCalendar.Domain.Entities
public string Title { get; private set; } = string.Empty; public string Title { get; private set; } = string.Empty;
public string Description{ get; private set; } = string.Empty; public string Description{ get; private set; } = string.Empty;
private List<Attendee> attendees = new List<Attendee>(); public IList<Attendee> Attendees { get; private set; } = new List<Attendee>();
public IEnumerable<Attendee> Attendees => attendees.AsReadOnly();
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; } public DateTime EndTime { get; private set; }
@@ -32,7 +31,7 @@ namespace PracticeCalendar.Domain.Entities
{ {
Guard.Against.Null(attendee, nameof(attendee)); Guard.Against.Null(attendee, nameof(attendee));
attendee.AssignToEvent(this.Id); attendee.AssignToEvent(this.Id);
attendees.Add(attendee); Attendees.Add(attendee);
var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee); var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee);
base.RegisterDomainEvent(attendeeAddedEvent); base.RegisterDomainEvent(attendeeAddedEvent);
@@ -6,7 +6,8 @@ namespace PracticeCalendar.Domain.Entities.Specifications
{ {
public PracticeEventsWithAttendees() public PracticeEventsWithAttendees()
{ {
Query.Include(x => x.Attendees); Query.AsNoTracking()
.Include(x => x.Attendees);
} }
} }
} }
@@ -0,0 +1,8 @@
using PracticeCalendar.Domain.Common;
namespace PracticeCalendar.Domain.Exceptions
{
public class PracticeEventNotFoundException : DomainException
{
}
}
@@ -5,13 +5,13 @@ using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Interfaces; using PracticeCalendar.Domain.Interfaces;
using PracticeCalendar.Infrastructure.Notification; using PracticeCalendar.Infrastructure.Notification;
using PracticeCalendar.Infrastructure.Persistence; using PracticeCalendar.Infrastructure.Persistence;
using System;
namespace PracticeCalendar.Infrastructure namespace PracticeCalendar.Infrastructure
{ {
public static class InfrastructureDI public static class ConfigureServices
{ {
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfigurationRoot configuration) public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
IConfigurationRoot configuration)
{ {
string connectionString = configuration.GetConnectionString("SqliteConnection"); string connectionString = configuration.GetConnectionString("SqliteConnection");
@@ -1,15 +1,14 @@
using FluentAssertions; using FluentAssertions;
using PracticeCalendar.Domain.Entities; using PracticeCalendar.Domain.Entities;
namespace PracticeCalendar.UnitTests namespace PracticeCalendar.UnitTests.Domain
{ {
public class PracticeEventTest public class PracticeEventTest
{ {
string _eventTitle = "Event1"; readonly string _eventTitle = "Event1";
string _eventDescription = "Description"; readonly string _eventDescription = "Description";
readonly string _attendeeName = "Claudiu Farcas";
string _attendeeName = "Claudiu Farcas"; readonly string _atendeeEmail = "claudiu.farcas@testingbee.com";
string _atendeeEmail = "claudiu.farcas@testingbee.com";
[Fact] [Fact]
public void InitializeProperties() public void InitializeProperties()
@@ -23,22 +22,26 @@ namespace PracticeCalendar.UnitTests
[Fact] [Fact]
public void InitializeWithNullShouldThrowException() public void InitializeWithNullShouldThrowException()
{ {
Action act = () => { Action act = () =>
var practiceEvent = new PracticeEvent(null, _eventDescription); {
var practiceEvent = new PracticeEvent(null!, _eventDescription);
}; };
act.Should().Throw<ArgumentNullException>(); act.Should().Throw<ArgumentNullException>();
act = () => { act = () =>
var practiceEvent = new PracticeEvent(_eventTitle, null); {
var practiceEvent = new PracticeEvent(_eventTitle, null!);
}; };
act.Should().Throw<ArgumentNullException>(); act.Should().Throw<ArgumentNullException>();
act = () => { act = () =>
{
var practiceEvent = new PracticeEvent(string.Empty, _eventDescription); var practiceEvent = new PracticeEvent(string.Empty, _eventDescription);
}; };
act.Should().Throw<ArgumentException>(); act.Should().Throw<ArgumentException>();
act = () => { act = () =>
{
var practiceEvent = new PracticeEvent(_eventTitle, string.Empty); var practiceEvent = new PracticeEvent(_eventTitle, string.Empty);
}; };
act.Should().Throw<ArgumentException>(); act.Should().Throw<ArgumentException>();
@@ -0,0 +1,12 @@
using static PracticeCalendar.UnitTests.Integration.Testing;
namespace PracticeCalendar.UnitTests.Integration
{
public class BaseTest
{
public BaseTest()
{
ResetState();
}
}
}
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PracticeCalendar.Infrastructure.Persistence;
namespace PracticeCalendar.UnitTests.Integration
{
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((builder, services) =>
{
services.Remove<DbContextOptions<ApplicationDbContext>>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDbForTesting")
);
});
}
}
}
@@ -0,0 +1,43 @@
using FluentAssertions;
using PracticeCalendar.Application.PracticeEvents.Commands;
using PracticeCalendar.Application.PracticeEvents.Queries;
using static PracticeCalendar.UnitTests.Integration.Testing;
namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
{
public class CreatePracticeEventsTest : BaseTest
{
[Fact]
public async Task ShouldCreatePracticeEvent()
{
await RunBeforeAnyTests();
var query = new CreatePracticeEventCommand(new PracticeEventDto
{
Title = "Some title",
Description = "Some desc",
StartTime = DateTime.Now,
EndTime = DateTime.Now,
Attendees = {
new AttendeeDto
{
Name = "Claudiu F",
EmailAddress = "claudiuf@somewhere.com"
},
new AttendeeDto
{
Name = "Claudiu F 2",
EmailAddress = "claudiuf2@somewhere.com"
}
}
});
var result = await SendAsync(query);
result.Should().NotBeNull();
result.Id.Should().NotBe(0);
result.Attendees.Count.Should().Be(2);
}
}
}
@@ -0,0 +1,48 @@
using FluentAssertions;
using PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents;
using PracticeCalendar.Domain.Entities;
using static PracticeCalendar.UnitTests.Integration.Testing;
namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
{
public class GetPracticeEventsTest : BaseTest
{
[Fact]
public async Task ShouldReturnZeroResult()
{
await RunBeforeAnyTests();
var query = new GetPracticeEventsQuery();
var result = await SendAsync(query);
result.Count.Should().Be(0);
}
[Fact]
public async Task ShouldReturnAllListsAndItems()
{
await RunBeforeAnyTests();
await AddAsync(new PracticeEvent("Test Event", "Event description")
{
Id = 1,
Attendees = {
new Attendee("Claudiu F", "claudiuf@busybee.com")
{
Id = 1
}
}
});
var query = new GetPracticeEventsQuery();
var result = await SendAsync(query);
result.Should().HaveCount(1);
result.First().Attendees.Should().HaveCount(1);
}
}
}
@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
namespace PracticeCalendar.UnitTests.Integration
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection Remove<TService>(this IServiceCollection services)
{
var serviceDescriptor = services.FirstOrDefault(d =>
d.ServiceType == typeof(TService));
if (serviceDescriptor != null)
{
services.Remove(serviceDescriptor);
}
return services;
}
}
}
@@ -0,0 +1,76 @@
using Microsoft.Extensions.Configuration;
using MediatR;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using PracticeCalendar.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using PracticeCalendar.Domain.Entities;
namespace PracticeCalendar.UnitTests.Integration
{
public partial class Testing
{
private static WebApplicationFactory<Program> _factory = null!;
private static IConfiguration _configuration = null!;
private static IServiceScopeFactory _scopeFactory = null!;
public static async Task RunBeforeAnyTests()
{
_factory = new CustomWebApplicationFactory();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
}
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
using var scope = _scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
return await mediator.Send(request);
}
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.FindAsync<TEntity>(keyValues);
}
public static async Task AddAsync<TEntity>(TEntity entity)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task<int> CountAsync<TEntity>() where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.Set<TEntity>().CountAsync();
}
public static void ResetState()
{
if (_scopeFactory != null)
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Set<Attendee>().RemoveRange(context.Set<Attendee>().ToList());
context.Set<PracticeEvent>().RemoveRange(context.Set<PracticeEvent>().ToList());
context.SaveChanges();
}
}
}
}
@@ -8,9 +8,24 @@
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<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">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -23,7 +38,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" /> <ProjectReference Include="..\PracticeCalendar.Api\PracticeCalendar.API.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Application\" />
</ItemGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"UseInMemoryDatabase": true, //integration tests always with real database
"ConnectionStrings": {
"TestSqliteConnection": "Data Source=practicecalendar_test.sqlite"
}
}
@@ -0,0 +1,4 @@
{
"parallelizeAssembly": false,
"parallelizeTestCollections": false
}