Skip to content

Commit 4bc138b

Browse files
committed
test: add comprehensive unit tests for handlers and Result pattern
Add test coverage for: - CreateEventCommandHandler (valid command and past date exception) - UpdateEventCommandHandler (existing event, not found, past date exception) - DeleteEventCommandHandler (existing and non-existing events) - GetEventByIdQueryHandler (cached, not cached, not found) - GetAllEventsQueryHandler (paged results and empty list) - CreateOrganizerCommandHandler - GetOrganizerByIdQueryHandler - Result<T> pattern (Success, Failure, implicit conversions, Map) Fix test issues: - Use correct repository method names (GetEventWithDetailsByIdAsync) - Use factory methods instead of constructors for Entities - Expect exceptions for business rule violations - Use consistent future dates to avoid clock skew issues
1 parent 2784595 commit 4bc138b

9 files changed

Lines changed: 510 additions & 3 deletions

EventFlow-API.Tests/GlobalUsing.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
global using Microsoft.EntityFrameworkCore;
66
global using Microsoft.AspNetCore.Mvc;
77

8+
global using EventFlow.Core.Commands;
9+
global using EventFlow.Core.Models;
10+
global using EventFlow.Core.Models.DTOs;
11+
global using EventFlow.Core.Services.Interfaces;
12+
global using EventFlow.Core.Models;
13+
global using EventFlow.Core.Primitives;
14+
global using EventFlow.Core.Repository;
15+
global using EventFlow.Core.ValueObjects;
16+
17+
global using EventFlow.Application.Services;
18+
global using EventFlow.Application.Validators;
19+
global using EventFlow.Application.Abstractions;
820
global using EventFlow.Application.Commands;
921
global using EventFlow.Application.DTOs;
1022
global using EventFlow.Application.Services;
1123
global using EventFlow.Application.Validators;
12-
global using EventFlow.Core.Models;
13-
global using EventFlow.Core.Repository;
14-
global using EventFlow.Core.ValueObjects;
24+
1525
global using EventFlow.Presentation.Controllers;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using EventFlow.Application.Features.Events.Commands.CreateEvent;
2+
using EventFlow.Core.Models;
3+
using EventFlow.Core.Primitives;
4+
using EventFlow.Core.Repository;
5+
using Moq;
6+
7+
namespace EventFlow.Tests.Handlers;
8+
9+
public class CreateEventCommandHandlerTests
10+
{
11+
private readonly Mock<IEventRepository> _mockRepo;
12+
private readonly Mock<IUnitOfWork> _mockUnitOfWork;
13+
private readonly CreateEventCommandHandler _handler;
14+
15+
public CreateEventCommandHandlerTests()
16+
{
17+
_mockRepo = new Mock<IEventRepository>();
18+
_mockUnitOfWork = new Mock<IUnitOfWork>();
19+
_handler = new CreateEventCommandHandler(_mockRepo.Object, _mockUnitOfWork.Object);
20+
}
21+
22+
[Fact]
23+
public async Task Handle_ValidCommand_ReturnsSuccessWithEventId()
24+
{
25+
var futureDate = DateTime.UtcNow.AddDays(2);
26+
var command = new CreateEventCommand(
27+
"Test Event",
28+
"Description",
29+
futureDate,
30+
"Location",
31+
1);
32+
33+
_mockRepo.Setup(r => r.PostAsync(It.IsAny<Event>()))
34+
.ReturnsAsync((Event e) => e);
35+
_mockUnitOfWork.Setup(u => u.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
36+
37+
var result = await _handler.Handle(command, CancellationToken.None);
38+
39+
Assert.True(result.IsSuccess);
40+
Assert.True(result.Value >= 0);
41+
_mockRepo.Verify(r => r.PostAsync(It.IsAny<Event>()), Times.Once);
42+
_mockUnitOfWork.Verify(u => u.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
43+
}
44+
45+
[Fact]
46+
public async Task Handle_PastDate_ThrowsBusinessRuleException()
47+
{
48+
var command = new CreateEventCommand(
49+
"Test Event",
50+
"Description",
51+
DateTime.UtcNow.AddDays(-1),
52+
"Location",
53+
1);
54+
55+
await Assert.ThrowsAsync<BusinessRuleValidationException>(() => _handler.Handle(command, CancellationToken.None));
56+
}
57+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using EventFlow.Application.Features.Organizers.Commands.CreateOrganizer;
2+
using EventFlow.Core.Models;
3+
using EventFlow.Core.Repository;
4+
using Moq;
5+
6+
namespace EventFlow.Tests.Handlers;
7+
8+
public class CreateOrganizerCommandHandlerTests
9+
{
10+
private readonly Mock<IOrganizerRepository> _mockRepo;
11+
private readonly Mock<IUnitOfWork> _mockUnitOfWork;
12+
private readonly CreateOrganizerCommandHandler _handler;
13+
14+
public CreateOrganizerCommandHandlerTests()
15+
{
16+
_mockRepo = new Mock<IOrganizerRepository>();
17+
_mockUnitOfWork = new Mock<IUnitOfWork>();
18+
_handler = new CreateOrganizerCommandHandler(_mockRepo.Object, _mockUnitOfWork.Object);
19+
}
20+
21+
[Fact]
22+
public async Task Handle_ValidCommand_ReturnsSuccessWithOrganizerId()
23+
{
24+
var command = new CreateOrganizerCommand("John Doe", "john@example.com");
25+
26+
_mockRepo.Setup(r => r.PostAsync(It.IsAny<Organizer>())).ReturnsAsync(Organizer.Create("John Doe", "john@example.com"));
27+
_mockUnitOfWork.Setup(u => u.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
28+
29+
var result = await _handler.Handle(command, CancellationToken.None);
30+
31+
Assert.True(result.IsSuccess);
32+
_mockRepo.Verify(r => r.PostAsync(It.IsAny<Organizer>()), Times.Once);
33+
}
34+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using EventFlow.Application.Features.Events.Commands.DeleteEvent;
2+
using EventFlow.Core.Models;
3+
using EventFlow.Core.Primitives;
4+
using EventFlow.Core.Repository;
5+
using Moq;
6+
7+
namespace EventFlow.Tests.Handlers;
8+
9+
public class DeleteEventCommandHandlerTests
10+
{
11+
private readonly Mock<IEventRepository> _mockRepo;
12+
private readonly Mock<IUnitOfWork> _mockUnitOfWork;
13+
private readonly Mock<ICacheService> _mockCache;
14+
private readonly DeleteEventCommandHandler _handler;
15+
16+
public DeleteEventCommandHandlerTests()
17+
{
18+
_mockRepo = new Mock<IEventRepository>();
19+
_mockUnitOfWork = new Mock<IUnitOfWork>();
20+
_mockCache = new Mock<ICacheService>();
21+
_handler = new DeleteEventCommandHandler(_mockRepo.Object, _mockUnitOfWork.Object, _mockCache.Object);
22+
}
23+
24+
[Fact]
25+
public async Task Handle_ExistingEvent_ReturnsSuccess()
26+
{
27+
var existingEvent = Event.Create("Title", "Desc", DateTime.UtcNow.AddDays(2), "Location", 1);
28+
var command = new DeleteEventCommand(1);
29+
30+
_mockRepo.Setup(r => r.GetEventByIdAsync(1)).ReturnsAsync(existingEvent);
31+
_mockRepo.Setup(r => r.DeleteAsync(1)).ReturnsAsync(1);
32+
_mockUnitOfWork.Setup(u => u.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
33+
_mockCache.Setup(c => c.RemoveAsync(It.IsAny<string>())).Returns(Task.CompletedTask);
34+
35+
var result = await _handler.Handle(command, CancellationToken.None);
36+
37+
Assert.True(result.IsSuccess);
38+
_mockRepo.Verify(r => r.DeleteAsync(1), Times.Once);
39+
_mockCache.Verify(c => c.RemoveAsync("event-1"), Times.Once);
40+
}
41+
42+
[Fact]
43+
public async Task Handle_NonExistingEvent_ReturnsNotFound()
44+
{
45+
var command = new DeleteEventCommand(999);
46+
_mockRepo.Setup(r => r.GetEventByIdAsync(999)).ReturnsAsync((Event?)null);
47+
48+
var result = await _handler.Handle(command, CancellationToken.None);
49+
50+
Assert.True(result.IsFailure);
51+
Assert.Equal(ErrorType.NotFound, result.Error.Type);
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using EventFlow.Application.DTOs;
2+
using EventFlow.Application.Features.Events.Queries.GetAllEvents;
3+
using EventFlow.Core.Models;
4+
using EventFlow.Core.Repository;
5+
using Moq;
6+
using AutoMapper;
7+
8+
namespace EventFlow.Tests.Handlers;
9+
10+
public class GetAllEventsQueryHandlerTests
11+
{
12+
private readonly Mock<IEventRepository> _mockRepo;
13+
private readonly Mock<IMapper> _mockMapper;
14+
private readonly GetAllEventsQueryHandler _handler;
15+
16+
public GetAllEventsQueryHandlerTests()
17+
{
18+
_mockRepo = new Mock<IEventRepository>();
19+
_mockMapper = new Mock<IMapper>();
20+
_handler = new GetAllEventsQueryHandler(_mockRepo.Object, _mockMapper.Object);
21+
}
22+
23+
[Fact]
24+
public async Task Handle_ReturnsPagedResult()
25+
{
26+
var events = new List<Event>
27+
{
28+
Event.Create("Event 1", "Desc 1", DateTime.UtcNow.AddDays(2), "Location 1", 1),
29+
Event.Create("Event 2", "Desc 2", DateTime.UtcNow.AddDays(3), "Location 2", 1)
30+
};
31+
32+
var pagedResult = new PagedResult<Event>(events, 1, 10, 2);
33+
var eventDtos = new List<EventDTO>
34+
{
35+
new() { Id = 1, Title = "Event 1" },
36+
new() { Id = 2, Title = "Event 2" }
37+
};
38+
39+
var query = new GetAllEventsQuery(new QueryParameters { PageNumber = 1, PageSize = 10 });
40+
41+
_mockRepo.Setup(r => r.GetAllPagedEventsAsync(It.IsAny<QueryParameters>())).ReturnsAsync(pagedResult);
42+
_mockMapper.Setup(m => m.Map<List<EventDTO>>(events)).Returns(eventDtos);
43+
44+
var result = await _handler.Handle(query, CancellationToken.None);
45+
46+
Assert.Equal(2, result.Items.Count);
47+
Assert.Equal(1, result.PageNumber);
48+
Assert.Equal(10, result.PageSize);
49+
Assert.Equal(2, result.TotalCount);
50+
}
51+
52+
[Fact]
53+
public async Task Handle_EmptyResult_ReturnsEmptyList()
54+
{
55+
var pagedResult = new PagedResult<Event>(new List<Event>(), 1, 10, 0);
56+
var query = new GetAllEventsQuery(new QueryParameters { PageNumber = 1, PageSize = 10 });
57+
58+
_mockRepo.Setup(r => r.GetAllPagedEventsAsync(It.IsAny<QueryParameters>())).ReturnsAsync(pagedResult);
59+
_mockMapper.Setup(m => m.Map<List<EventDTO>>(It.IsAny<List<Event>>())).Returns(new List<EventDTO>());
60+
61+
var result = await _handler.Handle(query, CancellationToken.None);
62+
63+
Assert.Empty(result.Items);
64+
Assert.Equal(0, result.TotalCount);
65+
}
66+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using EventFlow.Application.DTOs;
2+
using EventFlow.Application.Features.Events.Queries.GetEventById;
3+
using EventFlow.Core.Models;
4+
using EventFlow.Core.Primitives;
5+
using EventFlow.Core.Repository;
6+
using Moq;
7+
using AutoMapper;
8+
9+
namespace EventFlow.Tests.Handlers;
10+
11+
public class GetEventByIdQueryHandlerTests
12+
{
13+
private readonly Mock<IEventRepository> _mockRepo;
14+
private readonly Mock<IMapper> _mockMapper;
15+
private readonly Mock<ICacheService> _mockCache;
16+
private readonly GetEventByIdQueryHandler _handler;
17+
18+
public GetEventByIdQueryHandlerTests()
19+
{
20+
_mockRepo = new Mock<IEventRepository>();
21+
_mockMapper = new Mock<IMapper>();
22+
_mockCache = new Mock<ICacheService>();
23+
_handler = new GetEventByIdQueryHandler(_mockRepo.Object, _mockMapper.Object, _mockCache.Object);
24+
}
25+
26+
[Fact]
27+
public async Task Handle_CachedResult_ReturnsCachedValue()
28+
{
29+
var cachedDto = new EventDTO { Id = 1, Title = "Cached Event" };
30+
var query = new GetEventByIdQuery(1);
31+
32+
_mockCache.Setup(c => c.GetAsync<EventDTO>("event-1")).ReturnsAsync(cachedDto);
33+
34+
var result = await _handler.Handle(query, CancellationToken.None);
35+
36+
Assert.True(result.IsSuccess);
37+
Assert.Equal(cachedDto.Id, result.Value.Id);
38+
_mockRepo.Verify(r => r.GetEventByIdAsync(It.IsAny<int>()), Times.Never);
39+
}
40+
41+
[Fact]
42+
public async Task Handle_NotCached_ReturnsFromRepository()
43+
{
44+
var existingEvent = Event.Create("Title", "Desc", DateTime.UtcNow.AddDays(2), "Location", 1);
45+
var eventDto = new EventDTO { Id = 1, Title = "Title" };
46+
var query = new GetEventByIdQuery(1);
47+
48+
_mockCache.Setup(c => c.GetAsync<EventDTO>("event-1")).ReturnsAsync((EventDTO?)null);
49+
_mockRepo.Setup(r => r.GetEventWithDetailsByIdAsync(1)).ReturnsAsync(existingEvent);
50+
_mockMapper.Setup(m => m.Map<EventDTO>(existingEvent)).Returns(eventDto);
51+
_mockCache.Setup(c => c.SetAsync(It.IsAny<string>(), It.IsAny<EventDTO>(), It.IsAny<TimeSpan>())).Returns(Task.CompletedTask);
52+
53+
var result = await _handler.Handle(query, CancellationToken.None);
54+
55+
Assert.True(result.IsSuccess);
56+
Assert.Equal(1, result.Value.Id);
57+
_mockCache.Verify(c => c.SetAsync("event-1", eventDto, It.IsAny<TimeSpan>()), Times.Once);
58+
}
59+
60+
[Fact]
61+
public async Task Handle_NonExistingEvent_ReturnsNotFound()
62+
{
63+
var query = new GetEventByIdQuery(999);
64+
_mockCache.Setup(c => c.GetAsync<EventDTO>("event-999")).ReturnsAsync((EventDTO?)null);
65+
_mockRepo.Setup(r => r.GetEventByIdAsync(999)).ReturnsAsync((Event?)null);
66+
67+
var result = await _handler.Handle(query, CancellationToken.None);
68+
69+
Assert.True(result.IsFailure);
70+
Assert.Equal(ErrorType.NotFound, result.Error.Type);
71+
}
72+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using EventFlow.Application.DTOs;
2+
using EventFlow.Application.Features.Organizers.Queries.GetOrganizerById;
3+
using EventFlow.Core.Models;
4+
using EventFlow.Core.Primitives;
5+
using EventFlow.Core.Repository;
6+
using Moq;
7+
using AutoMapper;
8+
9+
namespace EventFlow.Tests.Handlers;
10+
11+
public class GetOrganizerByIdQueryHandlerTests
12+
{
13+
private readonly Mock<IOrganizerRepository> _mockRepo;
14+
private readonly Mock<IMapper> _mockMapper;
15+
private readonly Mock<ICacheService> _mockCache;
16+
private readonly GetOrganizerByIdQueryHandler _handler;
17+
18+
public GetOrganizerByIdQueryHandlerTests()
19+
{
20+
_mockRepo = new Mock<IOrganizerRepository>();
21+
_mockMapper = new Mock<IMapper>();
22+
_mockCache = new Mock<ICacheService>();
23+
_handler = new GetOrganizerByIdQueryHandler(_mockRepo.Object, _mockMapper.Object, _mockCache.Object);
24+
}
25+
26+
[Fact]
27+
public async Task Handle_ExistingOrganizer_ReturnsOrganizerDto()
28+
{
29+
var organizer = Organizer.Create("John Doe", "john@example.com");
30+
var organizerDto = new OrganizerDTO { Id = 1, Name = "John Doe", Email = "john@example.com" };
31+
var query = new GetOrganizerByIdQuery(1);
32+
33+
_mockCache.Setup(c => c.GetAsync<OrganizerDTO>("organizer-1")).ReturnsAsync((OrganizerDTO?)null);
34+
_mockRepo.Setup(r => r.GetOrganizerByIdAsync(1)).ReturnsAsync(organizer);
35+
_mockMapper.Setup(m => m.Map<OrganizerDTO>(organizer)).Returns(organizerDto);
36+
_mockCache.Setup(c => c.SetAsync(It.IsAny<string>(), It.IsAny<OrganizerDTO>(), It.IsAny<TimeSpan>())).Returns(Task.CompletedTask);
37+
38+
var result = await _handler.Handle(query, CancellationToken.None);
39+
40+
Assert.True(result.IsSuccess);
41+
Assert.Equal(1, result.Value.Id);
42+
}
43+
44+
[Fact]
45+
public async Task Handle_NonExistingOrganizer_ReturnsNotFound()
46+
{
47+
var query = new GetOrganizerByIdQuery(999);
48+
_mockCache.Setup(c => c.GetAsync<OrganizerDTO>("organizer-999")).ReturnsAsync((OrganizerDTO?)null);
49+
_mockRepo.Setup(r => r.GetOrganizerByIdAsync(999)).ReturnsAsync((Organizer?)null);
50+
51+
var result = await _handler.Handle(query, CancellationToken.None);
52+
53+
Assert.True(result.IsFailure);
54+
Assert.Equal(ErrorType.NotFound, result.Error.Type);
55+
}
56+
}

0 commit comments

Comments
 (0)