From c91e16be93c10f998d7051f7484eef166d42d711 Mon Sep 17 00:00:00 2001 From: Matej Vavrek <matvav13@gmail.com> Date: Tue, 12 Nov 2024 01:32:11 +0100 Subject: [PATCH] Created tests for RestaurantService, fixed a few mistakes --- Api/Controllers/RestaurantController.cs | 4 +- .../BusinessLayer.Tests.csproj | 1 + .../Services/RestaurantServiceTests.cs | 296 ++++++++++++++++++ .../DTOs/Restaurant/RestaurantDetailDto.cs | 29 +- .../ReviewAggregate/ReviewAggregateDTO.cs | 5 +- .../RestaurantService/IRestaurantService.cs | 7 +- .../RestaurantService/RestaurantService.cs | 17 +- DAL/Data/RestaurantDBContext.cs | 18 +- TestUtilities/FakeData/FakeDataInitializer.cs | 5 +- TestUtilities/Mocks/MockedDbContext.cs | 2 +- TestUtilities/TestUtilities.csproj | 2 +- pv179-project.sln | 12 +- 12 files changed, 351 insertions(+), 47 deletions(-) create mode 100644 BusinessLayer.Tests/Services/RestaurantServiceTests.cs diff --git a/Api/Controllers/RestaurantController.cs b/Api/Controllers/RestaurantController.cs index 2c3923e..a2295cf 100644 --- a/Api/Controllers/RestaurantController.cs +++ b/Api/Controllers/RestaurantController.cs @@ -59,8 +59,8 @@ namespace Api.Controllers [FromBody] RestaurantUpdateDto data) { if (data.Name is null && data.About is null - && data.Email is null && data.Web is null - && data.Phone is null && data.Category is null) + && data.Email is null && data.Web is null + && data.Phone is null && data.Category is null) { return BadRequest("No data was provided."); } diff --git a/BusinessLayer.Tests/BusinessLayer.Tests.csproj b/BusinessLayer.Tests/BusinessLayer.Tests.csproj index 71f2579..eb0347e 100644 --- a/BusinessLayer.Tests/BusinessLayer.Tests.csproj +++ b/BusinessLayer.Tests/BusinessLayer.Tests.csproj @@ -19,6 +19,7 @@ <ItemGroup> <ProjectReference Include="..\BusinessLayer\BusinessLayer.csproj" /> + <ProjectReference Include="..\TestUtilities\TestUtilities.csproj" /> </ItemGroup> <ItemGroup> diff --git a/BusinessLayer.Tests/Services/RestaurantServiceTests.cs b/BusinessLayer.Tests/Services/RestaurantServiceTests.cs new file mode 100644 index 0000000..2fd9086 --- /dev/null +++ b/BusinessLayer.Tests/Services/RestaurantServiceTests.cs @@ -0,0 +1,296 @@ +using BusinessLayer.DTOs.Location; +using BusinessLayer.DTOs.Restaurant; +using BusinessLayer.Services.RestaurantService; +using BusinessLayer.Utils.Filters; +using BusinessLayer.Utils.Ordering; +using DAL.Constants; +using DAL.Data; +using Microsoft.EntityFrameworkCore; +using TestUtilities.Mocks; + +namespace BusinessLayer.Tests.Services +{ + public class RestaurantServiceTests + { + private DbContextOptions<RestaurantDBContext> _dbContextOptions; + private RestaurantDBContext _dbContext; + private RestaurantService _service; + + public RestaurantServiceTests() + { + _dbContextOptions = MockedDbContext.CreateInMemoryContextOptions(); + _dbContext = MockedDbContext.CreateContext(_dbContextOptions); + _service = new RestaurantService(_dbContext); + } + + [Fact] + public async Task GetRestaurants_IdMatch() + { + // Arrange + var ids = new Guid[] { SeedingValues.Restaurant1Id, SeedingValues.Restaurant2Id }; + + // Act + var result = await _service.GetRestaurantsAsync(new RestaurantFilter(), RestaurantOrdering.CreatedAtDesc, 0, 0, ids); + + // Assert + Assert.Equal(ids.Length, result.Count); + Assert.All(ids, id => Assert.Contains(result, r => r.Id == id)); + } + + [Fact] + public async Task GetRestaurants_IdMatchNotExists_EmptyList() + { + // Arrange + var ids = new Guid[] { Guid.NewGuid() }; + + // Act + var result = await _service.GetRestaurantsAsync(new RestaurantFilter(), RestaurantOrdering.CreatedAtDesc, 0, 0, ids); + + // Assert + Assert.Empty(result); + } + + [Theory] + [InlineData(RestaurantOrdering.Name)] + [InlineData(RestaurantOrdering.NameDesc)] + [InlineData(RestaurantOrdering.CreatedAtDesc)] + public async Task GetRestaurants_OrderByName(RestaurantOrdering ordering) + { + // Arrange + // Whoops, nothing to arrange here! ÂŻ\_(ă„)_/ÂŻ + + // Act + var result = await _service.GetRestaurantsAsync(new RestaurantFilter(), ordering); + + // Assert + Assert.NotEmpty(result); + var first = result.First(); + foreach (var second in result.Skip(1)) + { + CheckOrdering(ordering, first, second); + } + } + + private static void CheckOrdering(RestaurantOrdering ordering, RestaurantDto first, RestaurantDto second) + { + switch (ordering) + { + case RestaurantOrdering.Name: + Assert.True(first.Name.CompareTo(second.Name) <= 0); + return; + case RestaurantOrdering.NameDesc: + Assert.True(first.Name.CompareTo(second.Name) >= 0); + return; + case RestaurantOrdering.CreatedAtDesc: + Assert.True(first.CreatedAt >= second.CreatedAt); + return; + } + } + + [Fact] + public async Task GetRestaurants_CategoryFiltering() + { + // Arrange + string expectedCategoryPart = "izza"; + RestaurantFilter ordering = new() + { + CategoryLike = expectedCategoryPart, + }; + + // Act + var result = await _service.GetRestaurantsAsync(ordering, RestaurantOrdering.CreatedAtDesc); + + // Assert + Assert.NotEmpty(result); + Assert.Single(result); + Assert.All(result, r => r.Category.Contains(expectedCategoryPart)); + } + + [Fact] + public async Task GetRestaurants_NameFiltering() + { + // Arrange + string expectedNamePart = "Doe"; + RestaurantFilter ordering = new() + { + NameLike = expectedNamePart, + }; + + // Act + var result = await _service.GetRestaurantsAsync(ordering, RestaurantOrdering.CreatedAtDesc); + + // Assert + Assert.NotEmpty(result); + Assert.Single(result); + Assert.All(result, r => r.Name.Contains(expectedNamePart)); + } + + [Fact] + public async Task GetRestaurant_Exists() + { + // Arrange + Guid expectedId = SeedingValues.Restaurant1Id; + + // Act + var result = await _service.GetRestaurantAsync(expectedId); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedId, result.Id); + Assert.Null(result.DeletedAt); + } + + [Fact] + public async Task GetRestaurant_NotExists() + { + // Arrange + Guid notExistsId = Guid.NewGuid(); + + // Act + var result = await _service.GetRestaurantAsync(notExistsId); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task CreateRestaurant_Valid_Succeeds() + { + // Arrange + var mainterId = SeedingValues.User1Id; + const string name = "OloMocTvaruzku"; + const string about = "Come eat your heart out in our all you can eat menu composed mostly of variants of olomoucke tvaruzky."; + const string category = "CHEESE!!!!!"; + + var location = new LocationCreateDto() + { + Country = "Czech Republic", + City = "Brno", + Address = "Holandska 68a", + Zipcode = "60200" + }; + + var data = new RestaurantCreateDto() + { + MaintainerId = mainterId, + Name = name, + About = about, + Category = category, + Location = location + }; + + // Act + var result = await _service.CreateRestaurantAsync(data); + + // Assert + Assert.NotNull(result); + Assert.Equal(name, result.Name); + Assert.Equal(about, result.About); + Assert.Equal(category, result.Category); + Assert.Null(result.DeletedAt); + } + + [Fact] + public async Task CreateRestaurant_MaintainerNotExists_Fails() + { + // Arrange + var mainterId = SeedingValues.User1Id; + const string name = "OloMocTvaruzku"; + const string about = "Come eat your heart out in our all you can eat menu composed mostly of variants of olomoucke tvaruzky."; + const string category = "CHEESE!!!!!"; + + var location = new LocationCreateDto() + { + Country = "Czech Republic", + City = "Brno", + Address = "Holandska 68a", + Zipcode = "60200" + }; + + var data = new RestaurantCreateDto() + { + MaintainerId = mainterId, + Name = name, + About = about, + Category = category, + Location = location + }; + + // Act + var result = await _service.CreateRestaurantAsync(data); + + // Assert + Assert.NotNull(result); + Assert.Equal(name, result.Name); + Assert.Equal(about, result.About); + Assert.Equal(category, result.Category); + Assert.Null(result.DeletedAt); + } + + [Fact] + public async Task UpdateRestaurant_Valid_Succeeds() + { + // Arrange + string name = "MyRestaurant.exe"; + var restaurantData = new RestaurantUpdateDto + { + Name = name + }; + + var restaurantId = SeedingValues.Restaurant1Id; + + // Act + var result = await _service.UpdateRestaurantAsync(restaurantId, restaurantData); + + // Assert + Assert.NotNull(result); + Assert.Equal(name, result.Name); + Assert.NotEqual(result.CreatedAt, result.UpdatedAt); + } + + [Fact] + public async Task UpdateRestaurant_NotExists_Fails() + { + // Arrange + string name = "MyRestaurant.exe"; + var restaurantData = new RestaurantUpdateDto + { + Name = name + }; + + var restaurantId = Guid.NewGuid(); + + // Act + var result = await _service.UpdateRestaurantAsync(restaurantId, restaurantData); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task DeleteRestaurant_Exists_Succeeds() + { + // Arrange + var restaurantId = SeedingValues.Restaurant1Id; + + // Act + var result = await _service.DeleteRestaurantAsync(restaurantId, true); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task DeletedRestaurant_NotExists_Fails() + { + // Arrange + var restaurantId = Guid.NewGuid(); + + // Act + var result = await _service.DeleteRestaurantAsync(restaurantId, true); + + // Assert + Assert.False(result); + } + } +} diff --git a/BusinessLayer/DTOs/Restaurant/RestaurantDetailDto.cs b/BusinessLayer/DTOs/Restaurant/RestaurantDetailDto.cs index 3e7f153..c4220b2 100644 --- a/BusinessLayer/DTOs/Restaurant/RestaurantDetailDto.cs +++ b/BusinessLayer/DTOs/Restaurant/RestaurantDetailDto.cs @@ -6,21 +6,18 @@ namespace BusinessLayer.DTOs.Restaurant { public record RestaurantDetailDto { - public record RestaurantDto - { - public Guid Id { get; set; } - public required LocationDto Location { get; set; } - public ReviewAggregateDTO? ReviewAggregate { get; set; } - public required List<UserDto> RestaurantMaintainers { get; set; } - public required string Name { get; set; } - public string? About { get; set; } - public string? Phone { get; set; } - public string? Email { get; set; } - public string? Web { get; set; } - public required string Category { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public DateTime? DeletedAt { get; set; } - } + public Guid Id { get; set; } + public required LocationDto Location { get; set; } + public ReviewAggregateDTO? ReviewAggregate { get; set; } + public required List<UserDto> RestaurantMaintainers { get; set; } + public required string Name { get; set; } + public string? About { get; set; } + public string? Phone { get; set; } + public string? Email { get; set; } + public string? Web { get; set; } + public required string Category { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public DateTime? DeletedAt { get; set; } } } \ No newline at end of file diff --git a/BusinessLayer/DTOs/ReviewAggregate/ReviewAggregateDTO.cs b/BusinessLayer/DTOs/ReviewAggregate/ReviewAggregateDTO.cs index 20f7c25..b6261b5 100644 --- a/BusinessLayer/DTOs/ReviewAggregate/ReviewAggregateDTO.cs +++ b/BusinessLayer/DTOs/ReviewAggregate/ReviewAggregateDTO.cs @@ -1,11 +1,8 @@ -using BusinessLayer.DTOs.Restaurant; - -namespace BusinessLayer.DTOs.ReviewAggregate +namespace BusinessLayer.DTOs.ReviewAggregate { public class ReviewAggregateDTO { public Guid RestaurantId { get; set; } - public RestaurantDto? Restaurant { get; set; } public uint FoodRating { get; set; } public uint ServiceRating { get; set; } public uint EnvironmentRating { get; set; } diff --git a/BusinessLayer/Services/RestaurantService/IRestaurantService.cs b/BusinessLayer/Services/RestaurantService/IRestaurantService.cs index 5c356b7..c37174c 100644 --- a/BusinessLayer/Services/RestaurantService/IRestaurantService.cs +++ b/BusinessLayer/Services/RestaurantService/IRestaurantService.cs @@ -13,10 +13,13 @@ namespace BusinessLayer.Services.RestaurantService int offset = 0, Guid[]? ids = null); public Task<RestaurantDetailDto?> GetRestaurantAsync(Guid id); - public Task<RestaurantDetailDto?> CreateRestaurantAsync(RestaurantCreateDto restaurant); + public Task<RestaurantDetailDto?> CreateRestaurantAsync( + RestaurantCreateDto restaurant, + bool save = true); public Task<RestaurantDetailDto?> UpdateRestaurantAsync( Guid id, - RestaurantUpdateDto restaurant); + RestaurantUpdateDto restaurant, + bool save = true); public Task<bool> DeleteRestaurantAsync( Guid id, bool save = true); diff --git a/BusinessLayer/Services/RestaurantService/RestaurantService.cs b/BusinessLayer/Services/RestaurantService/RestaurantService.cs index dc68e93..ecc65c3 100644 --- a/BusinessLayer/Services/RestaurantService/RestaurantService.cs +++ b/BusinessLayer/Services/RestaurantService/RestaurantService.cs @@ -23,9 +23,10 @@ namespace BusinessLayer.Services.RestaurantService return await _dbContext.Restaurants .Include(r => r.ReviewAggregate) .Where(filter.ComposeFilterFunc(ids)) - .ApplyPagination(limit, offset) .ApplyOrdering(orderBy) - .Select(r => r.Adapt<RestaurantDto>()).ToListAsync(); + .ApplyPagination(limit, offset) + .Select(r => r.Adapt<RestaurantDto>()) + .ToListAsync(); } public async Task<RestaurantDetailDto?> GetRestaurantAsync(Guid id) @@ -45,7 +46,8 @@ namespace BusinessLayer.Services.RestaurantService return restaurant.Adapt<RestaurantDetailDto>(); } - public async Task<RestaurantDetailDto?> CreateRestaurantAsync(RestaurantCreateDto data) + public async Task<RestaurantDetailDto?> CreateRestaurantAsync( + RestaurantCreateDto data, bool save = true) { var maintainer = await _dbContext.Users.FindAsync(data.MaintainerId); @@ -78,12 +80,13 @@ namespace BusinessLayer.Services.RestaurantService await _dbContext.Restaurants.AddAsync(restaurant); maintainer.MaintainedRestaurants.Add(restaurant); - await _dbContext.SaveChangesAsync(); + await SaveAsync(save); return restaurant.Adapt<RestaurantDetailDto>(); } - public async Task<RestaurantDetailDto?> UpdateRestaurantAsync(Guid id, RestaurantUpdateDto data) + public async Task<RestaurantDetailDto?> UpdateRestaurantAsync( + Guid id, RestaurantUpdateDto data, bool save = true) { var restaurant = await _dbContext.Restaurants.FindAsync(id); @@ -101,7 +104,7 @@ namespace BusinessLayer.Services.RestaurantService restaurant.UpdatedAt = DateTime.UtcNow; _dbContext.Restaurants.Update(restaurant); - await _dbContext.SaveChangesAsync(); + await SaveAsync(save); return restaurant.Adapt<RestaurantDetailDto>(); } @@ -131,7 +134,7 @@ namespace BusinessLayer.Services.RestaurantService _dbContext.Restaurants.Update(restaurant); _dbContext.Locations.Update(location); - await _dbContext.SaveChangesAsync(save); + await SaveAsync(save); return true; } diff --git a/DAL/Data/RestaurantDBContext.cs b/DAL/Data/RestaurantDBContext.cs index 1b380f3..520a700 100644 --- a/DAL/Data/RestaurantDBContext.cs +++ b/DAL/Data/RestaurantDBContext.cs @@ -5,17 +5,17 @@ namespace DAL.Data { public class RestaurantDBContext : DbContext { - public DbSet<User> Users { get; set; } - public DbSet<Review> Reviews { get; set; } - public DbSet<ReviewComment> ReviewComments { get; set; } - public DbSet<ReviewAggregateResult> ReviewAggregateResults { get; set; } - public DbSet<Event> Events { get; set; } - public DbSet<EventComment> EventComments { get; set; } - public DbSet<EventParticipant> EventParticipants { get; set; } + public virtual DbSet<User> Users { get; set; } + public virtual DbSet<Review> Reviews { get; set; } + public virtual DbSet<ReviewComment> ReviewComments { get; set; } + public virtual DbSet<ReviewAggregateResult> ReviewAggregateResults { get; set; } + public virtual DbSet<Event> Events { get; set; } + public virtual DbSet<EventComment> EventComments { get; set; } + public virtual DbSet<EventParticipant> EventParticipants { get; set; } - public DbSet<Restaurant> Restaurants { get; set; } + public virtual DbSet<Restaurant> Restaurants { get; set; } - public DbSet<Location> Locations { get; set; } + public virtual DbSet<Location> Locations { get; set; } public RestaurantDBContext(DbContextOptions<RestaurantDBContext> options) : base(options) { } diff --git a/TestUtilities/FakeData/FakeDataInitializer.cs b/TestUtilities/FakeData/FakeDataInitializer.cs index 201bca8..7c05718 100644 --- a/TestUtilities/FakeData/FakeDataInitializer.cs +++ b/TestUtilities/FakeData/FakeDataInitializer.cs @@ -21,7 +21,7 @@ namespace TestUtilities.FakeData var reviewAggregates = reviews .GroupBy(r => r.RestaurantId) - .Select(group => new ReviewAggregate + .Select(group => new ReviewAggregateResult { RestaurantId = group.Key, FoodRating = (uint)Math.Round(group.Average(item => item.FoodRating)), @@ -29,7 +29,8 @@ namespace TestUtilities.FakeData EnvironmentRating = (uint)Math.Round(group.Average(item => item.EnvironmentRating)) }); - context.ReviewAggregate.AddRange(reviewAggregates); + context.ReviewAggregateResults.AddRange(reviewAggregates); + context.SaveChanges(); } private static List<User> PrepareUserModels() { diff --git a/TestUtilities/Mocks/MockedDbContext.cs b/TestUtilities/Mocks/MockedDbContext.cs index 56ddac6..14bc67c 100644 --- a/TestUtilities/Mocks/MockedDbContext.cs +++ b/TestUtilities/Mocks/MockedDbContext.cs @@ -11,7 +11,7 @@ namespace TestUtilities.Mocks public static DbContextOptions<RestaurantDBContext> CreateInMemoryContextOptions() { return new DbContextOptionsBuilder<RestaurantDBContext>() - .UseInMemoryDatabase(DBName).Options; + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options; } public static RestaurantDBContext CreateContext(DbContextOptions<RestaurantDBContext> options) diff --git a/TestUtilities/TestUtilities.csproj b/TestUtilities/TestUtilities.csproj index d672305..947d4fb 100644 --- a/TestUtilities/TestUtilities.csproj +++ b/TestUtilities/TestUtilities.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="EntityFrameworkCore.Testing.NSubstitute" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.10" /> - <PackageReference Include="NSubstitute" Version="5.3.0" /> </ItemGroup> <ItemGroup> diff --git a/pv179-project.sln b/pv179-project.sln index 6475a2f..fb9b03f 100644 --- a/pv179-project.sln +++ b/pv179-project.sln @@ -7,11 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api", "Api\Api.csproj", "{3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DAL", "DAL\DAL.csproj", "{5BA37FEF-E2CC-43A8-8D69-FD40FB2B9454}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLayer", "BusinessLayer\BusinessLayer.csproj", "{539D3A8E-5CD3-4236-81B0-C36537BC6B68}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusinessLayer", "BusinessLayer\BusinessLayer.csproj", "{539D3A8E-5CD3-4236-81B0-C36537BC6B68}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MVC", "MVC\MVC.csproj", "{11AD9763-446E-4FD8-AC2D-BF092CAE16DE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MVC", "MVC\MVC.csproj", "{11AD9763-446E-4FD8-AC2D-BF092CAE16DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLayer.Tests", "BusinessLayer.Tests\BusinessLayer.Tests.csproj", "{B244DC7E-77CF-4B9A-B382-BC8E1E4B057C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusinessLayer.Tests", "BusinessLayer.Tests\BusinessLayer.Tests.csproj", "{B244DC7E-77CF-4B9A-B382-BC8E1E4B057C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "TestUtilities\TestUtilities.csproj", "{7F02D143-33B1-4A83-BE5E-8AE192DA6A8D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {B244DC7E-77CF-4B9A-B382-BC8E1E4B057C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B244DC7E-77CF-4B9A-B382-BC8E1E4B057C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B244DC7E-77CF-4B9A-B382-BC8E1E4B057C}.Release|Any CPU.Build.0 = Release|Any CPU + {7F02D143-33B1-4A83-BE5E-8AE192DA6A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F02D143-33B1-4A83-BE5E-8AE192DA6A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F02D143-33B1-4A83-BE5E-8AE192DA6A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F02D143-33B1-4A83-BE5E-8AE192DA6A8D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE -- GitLab