220 lines
7.7 KiB
C#
220 lines
7.7 KiB
C#
using AutoMapper;
|
|
using Koogle.Application.DTOs;
|
|
using Koogle.Application.Interfaces;
|
|
using Koogle.Application.Services;
|
|
using Koogle.Domain.Entities;
|
|
using Koogle.Domain.Interfaces;
|
|
using Koogle.Tests.Common;
|
|
using KoogleApp.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Diagnostics;
|
|
|
|
namespace Koogle.Tests.Performance;
|
|
|
|
/// <summary>
|
|
/// Performance tests for critical service operations.
|
|
/// </summary>
|
|
public class ServicePerformanceTests
|
|
{
|
|
private const int SmallDataset = 100;
|
|
private const int MediumDataset = 1000;
|
|
private const int LargeDataset = 10000;
|
|
|
|
#region ClubService Performance Tests
|
|
|
|
[Theory]
|
|
[InlineData(SmallDataset, 50)] // 100 records should complete in 50ms
|
|
[InlineData(MediumDataset, 200)] // 1000 records should complete in 200ms
|
|
public async Task GetAllAsync_CompletesWithinTimeLimit(int recordCount, int maxMilliseconds)
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase($"PerfTest_{Guid.NewGuid()}")
|
|
.Options;
|
|
|
|
await using var context = new AppDbContext(options);
|
|
|
|
// Seed data
|
|
var clubs = Enumerable.Range(0, recordCount)
|
|
.Select(_ => TestDataGenerator.CreateClub())
|
|
.ToList();
|
|
await context.Clubs.AddRangeAsync(clubs);
|
|
await context.SaveChangesAsync();
|
|
|
|
var mapper = TestMockHelpers.CreateClubMapperMock().Object;
|
|
|
|
var repositoryMock = new Mock<IClubRepository>();
|
|
repositoryMock.Setup(r => r.GetAllAsync(It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(() => context.Clubs.Where(c => !c.IsDeleted).ToList());
|
|
|
|
var gifServiceMock = new Mock<IClubGifService>();
|
|
var bookingCategoryServiceMock = new Mock<IBookingCategoryService>();
|
|
var service = new ClubService(repositoryMock.Object, mapper, context, gifServiceMock.Object, bookingCategoryServiceMock.Object);
|
|
|
|
// Act
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var result = await service.GetAllAsync();
|
|
stopwatch.Stop();
|
|
|
|
// Assert
|
|
result.Should().HaveCount(recordCount);
|
|
stopwatch.ElapsedMilliseconds.Should().BeLessThan(maxMilliseconds,
|
|
$"GetAllAsync with {recordCount} records should complete within {maxMilliseconds}ms, took {stopwatch.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_CompletesQuickly()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase($"PerfTest_CreateAsync_{Guid.NewGuid()}")
|
|
.Options;
|
|
|
|
await using var context = new AppDbContext(options);
|
|
|
|
var repositoryMock = new Mock<IClubRepository>();
|
|
repositoryMock.Setup(r => r.AddAsync(It.IsAny<Club>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync((Club c, CancellationToken _) => c);
|
|
|
|
var mapperMock = TestMockHelpers.CreateClubMapperMock();
|
|
var gifServiceMock = new Mock<IClubGifService>();
|
|
var bookingCategoryServiceMock = new Mock<IBookingCategoryService>();
|
|
var service = new ClubService(repositoryMock.Object, mapperMock.Object, context, gifServiceMock.Object, bookingCategoryServiceMock.Object);
|
|
var dto = TestDataGenerator.CreateCreateClubDto();
|
|
|
|
// Act
|
|
var stopwatch = Stopwatch.StartNew();
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
await service.CreateAsync(dto);
|
|
}
|
|
stopwatch.Stop();
|
|
|
|
// Assert - 100 creates should complete in under 100ms (1ms per create)
|
|
stopwatch.ElapsedMilliseconds.Should().BeLessThan(100,
|
|
$"100 CreateAsync calls should complete within 100ms, took {stopwatch.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AutoMapper Performance Tests
|
|
|
|
[Theory]
|
|
[InlineData(SmallDataset, 50)]
|
|
[InlineData(MediumDataset, 200)]
|
|
[InlineData(LargeDataset, 1000)]
|
|
public void Mapper_MapsClubsToDtos_Efficiently(int count, int maxMilliseconds)
|
|
{
|
|
// Arrange
|
|
var mapper = TestMockHelpers.CreateClubMapperMock().Object;
|
|
var clubs = Enumerable.Range(0, count)
|
|
.Select(_ => TestDataGenerator.CreateClub())
|
|
.ToList();
|
|
|
|
// Act
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var result = mapper.Map<List<ClubDto>>(clubs);
|
|
stopwatch.Stop();
|
|
|
|
// Assert
|
|
result.Should().HaveCount(count);
|
|
stopwatch.ElapsedMilliseconds.Should().BeLessThan(maxMilliseconds,
|
|
$"Mapping {count} clubs should complete within {maxMilliseconds}ms, took {stopwatch.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Fluxor Reducer Performance Tests
|
|
|
|
[Theory]
|
|
[InlineData(SmallDataset)]
|
|
[InlineData(MediumDataset)]
|
|
public void Reducers_HandleLargeStateLists_Efficiently(int clubCount)
|
|
{
|
|
// Arrange
|
|
var clubs = TestDataGenerator.CreateClubDtos(clubCount);
|
|
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
|
|
var newClub = TestDataGenerator.CreateClubDto();
|
|
|
|
// Act - Measure time for add operation
|
|
var stopwatch = Stopwatch.StartNew();
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
Koogle.Web.Store.ClubState.ClubReducers.OnCreateClubSuccess(
|
|
state,
|
|
new Koogle.Web.Store.ClubState.CreateClubSuccessAction(newClub));
|
|
}
|
|
stopwatch.Stop();
|
|
|
|
// Assert - 100 reducer calls should complete quickly
|
|
stopwatch.ElapsedMilliseconds.Should().BeLessThan(50,
|
|
$"100 reducer calls with {clubCount} existing items should complete within 50ms");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(SmallDataset)]
|
|
[InlineData(MediumDataset)]
|
|
public void Reducers_DeleteFromLargeList_Efficiently(int clubCount)
|
|
{
|
|
// Arrange
|
|
var clubs = TestDataGenerator.CreateClubDtos(clubCount);
|
|
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
|
|
var deleteIds = clubs.Take(100).Select(c => c.Id).ToList();
|
|
|
|
// Act - Measure time for delete operations
|
|
var stopwatch = Stopwatch.StartNew();
|
|
foreach (var id in deleteIds)
|
|
{
|
|
Koogle.Web.Store.ClubState.ClubReducers.OnDeleteClubSuccess(
|
|
state,
|
|
new Koogle.Web.Store.ClubState.DeleteClubSuccessAction(id));
|
|
}
|
|
stopwatch.Stop();
|
|
|
|
// Assert
|
|
stopwatch.ElapsedMilliseconds.Should().BeLessThan(100,
|
|
$"100 delete operations on {clubCount} items should complete within 100ms");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Memory Allocation Tests
|
|
|
|
[Fact]
|
|
public void LargeDatasetOperations_DoNotCauseExcessiveAllocation()
|
|
{
|
|
// Arrange
|
|
var clubs = TestDataGenerator.CreateClubDtos(MediumDataset);
|
|
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
|
|
|
|
// Force GC to get baseline
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
|
|
var memoryBefore = GC.GetTotalMemory(true);
|
|
|
|
// Act - Perform many operations
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
var newClub = TestDataGenerator.CreateClubDto();
|
|
Koogle.Web.Store.ClubState.ClubReducers.OnCreateClubSuccess(
|
|
state,
|
|
new Koogle.Web.Store.ClubState.CreateClubSuccessAction(newClub));
|
|
}
|
|
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
|
|
var memoryAfter = GC.GetTotalMemory(true);
|
|
var memoryIncrease = memoryAfter - memoryBefore;
|
|
|
|
// Assert - Memory increase should be reasonable (< 50MB for 100 operations)
|
|
memoryIncrease.Should().BeLessThan(50 * 1024 * 1024,
|
|
$"Memory increase should be less than 50MB, was {memoryIncrease / 1024 / 1024}MB");
|
|
}
|
|
|
|
#endregion
|
|
}
|