Unit tests + Dockerfile

This commit is contained in:
Alejandro Sarmiento
2024-02-29 21:52:46 +01:00
parent 81a3e91d6e
commit 9b5ddee951
19 changed files with 422 additions and 49 deletions

View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
@@ -11,7 +12,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,28 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["CleanArchitecture.API/CleanArchitecture.API.csproj", "CleanArchitecture.API/"]
COPY ["CleanArchitecture.Application/CleanArchitecture.Application.csproj", "CleanArchitecture.Application/"]
COPY ["CleanArchitecture.Domain/CleanArchitecture.Domain.csproj", "CleanArchitecture.Domain/"]
COPY ["CleanArchitecture.Data/CleanArchitecture.Infrastructure.csproj", "CleanArchitecture.Data/"]
COPY ["CleanArchitecture.Identity/CleanArchitecture.Identity.csproj", "CleanArchitecture.Identity/"]
RUN dotnet restore "./CleanArchitecture.API/CleanArchitecture.API.csproj"
COPY . .
WORKDIR "/src/CleanArchitecture.API"
RUN dotnet build "./CleanArchitecture.API.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./CleanArchitecture.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CleanArchitecture.API.dll"]

View File

@@ -1,23 +1,14 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:32905",
"sslPort": 0
}
},
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5124",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:14563"
},
"IIS Express": {
"commandName": "IISExpress",
@@ -26,6 +17,24 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "80"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:22080",
"sslPort": 0
}
}
}
}

View File

@@ -27,8 +27,4 @@
<ProjectReference Include="..\CleanArchitecture.Data\CleanArchitecture.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Features\Video\Commands\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,51 @@
using AutoMapper;
using CleanArchitecture.Application.Contracts.Infrastructure;
using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer;
using Shouldly;
using CleanArchitecture.Domain;
using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer;
namespace CleanArchitecture.Application.UnitTests.Features.Streamers.Commands.CreateStreamer
{
public class CreateStreamerCommandHandlerXUnitTests
{
private readonly IMapper mapper;
private readonly Mock<UnitOfWork> mockUnitOfWork;
private readonly Mock<IEmailService> emailService;
private readonly Mock<ILogger<CreateStreamerCommandHandler>> logger;
public CreateStreamerCommandHandlerXUnitTests()
{
mockUnitOfWork = MockUnitOfWork.GetUnitOfWork();
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
emailService = new Mock<IEmailService>();
logger = new Mock<ILogger<CreateStreamerCommandHandler>>();
}
[Fact]
public async Task CreateStreamerTest()
{
var handler = new CreateStreamerCommandHandler(mockUnitOfWork.Object, mapper, emailService.Object, logger.Object);
var request = new CreateStreamerCommand()
{
Nombre = "AlexStream",
Url = "https://AlexStream.com"
};
var result = await handler.Handle(request, CancellationToken.None);
result.ShouldBeOfType<Streamer>();
result.Nombre.ShouldBe("AlexStream");
}
}
}

View File

@@ -0,0 +1,52 @@
using AutoMapper;
using CleanArchitecture.Application.Contracts.Infrastructure;
using CleanArchitecture.Application.Features.Streamers.Commands.DeleteStreamer;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Domain;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Moq;
using Shouldly;
using Xunit;
namespace CleanArchitecture.Application.UnitTests.Features.Streamers.Commands.DeleteStreamer
{
public class DeleteVideoCommandHandlerXUnitTests
{
private readonly IMapper mapper;
private readonly Mock<UnitOfWork> mockUnitOfWork;
private readonly Mock<IEmailService> emailService;
private readonly Mock<ILogger<DeleteStreamerCommandHandler>> logger;
public DeleteVideoCommandHandlerXUnitTests()
{
mockUnitOfWork = MockUnitOfWork.GetUnitOfWork();
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
emailService = new Mock<IEmailService>();
logger = new Mock<ILogger<DeleteStreamerCommandHandler>>();
MockStreamerRepository.AddDataStreamerRepository(mockUnitOfWork.Object.StreamerDbContext);
}
[Fact]
public async Task UpdateStreamerTest()
{
var handler = new DeleteStreamerCommandHandler(mockUnitOfWork.Object, mapper, logger.Object);
var request = new DeleteStreamerCommand()
{
Id = Guid.Parse("edfe00d5-7599-4788-b52a-acc2a683b188")
};
MediatR.Unit result = await handler.Handle(request, CancellationToken.None);
if (result == null)
{
throw new Exception("Result is null");
}
result.ShouldBeOfType<MediatR.Unit>();
}
}
}

View File

@@ -0,0 +1,61 @@
using AutoMapper;
using CleanArchitecture.Application.Contracts.Infrastructure;
using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Domain;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Moq;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace CleanArchitecture.Application.UnitTests.Features.Streamers.Commands.UpdateStreamer
{
public class UpdateStreamerCommandHandlerXUnitTests
{
private readonly IMapper mapper;
private readonly Mock<UnitOfWork> mockUnitOfWork;
private readonly Mock<IEmailService> emailService;
private readonly Mock<ILogger<UpdateStreamerCommandHandler>> logger;
public UpdateStreamerCommandHandlerXUnitTests()
{
mockUnitOfWork = MockUnitOfWork.GetUnitOfWork();
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
emailService = new Mock<IEmailService>();
logger = new Mock<ILogger<UpdateStreamerCommandHandler>>();
MockStreamerRepository.AddDataStreamerRepository(mockUnitOfWork.Object.StreamerDbContext);
}
[Fact]
public async Task UpdateStreamerTest()
{
var handler = new UpdateStreamerCommandHandler(mockUnitOfWork.Object, mapper, logger.Object);
var request = new UpdateStreamerCommand()
{
Id = Guid.Parse("edfe00d5-7599-4788-b52a-acc2a683b188"),
Nombre = "AlexStream2",
Url = "https://AlexStream2.com"
};
var result = await handler.Handle(request, CancellationToken.None);
if(result == null)
{
throw new Exception("Result is null");
}
result.ShouldBeOfType<Streamer>();
result.Nombre.ShouldBe("AlexStream2");
}
}
}

View File

@@ -0,0 +1,49 @@
using AutoMapper;
using CleanArchitecture.Application.Contracts.Infrastructure;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using Shouldly;
using CleanArchitecture.Application.Features.Videos.Commands.CreateVideo;
namespace CleanArchitecture.Application.UnitTests.Features.Videos.Commands.CreateStreamer
{
public class CreateVideoCommandHandlerXUnitTests
{
private readonly IMapper mapper;
private readonly Mock<UnitOfWork> mockUnitOfWork;
private readonly Mock<IEmailService> emailService;
private readonly Mock<ILogger<CreateVideoCommandHandler>> logger;
public CreateVideoCommandHandlerXUnitTests()
{
mockUnitOfWork = MockUnitOfWork.GetUnitOfWork();
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
emailService = new Mock<IEmailService>();
logger = new Mock<ILogger<CreateVideoCommandHandler>>();
}
[Fact]
public async Task CreateVideoTest()
{
var handler = new CreateVideoCommandHandler(mockUnitOfWork.Object, mapper, emailService.Object, logger.Object);
var request = new CreateVideoCommand()
{
Nombre = "Video de ALex"
};
Domain.Video result = await handler.Handle(request, CancellationToken.None);
result.ShouldBeOfType<Domain.Video>();
result.Nombre.ShouldBe("Video de ALex");
}
}
}

View File

@@ -0,0 +1,6 @@
namespace CleanArchitecture.Application.UnitTests.Features.Videos.Commands.DeleteStreamer
{
public class DeleteVideoCommandHandlerXUnitTests
{
}
}

View File

@@ -0,0 +1,61 @@
using AutoMapper;
using CleanArchitecture.Application.Contracts.Infrastructure;
using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer;
using CleanArchitecture.Application.Features.Videos.Commands.UpdateVideo;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Domain;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using Moq;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace CleanArchitecture.Application.UnitTests.Features.Videos.Commands.UpdateStreamer
{
public class UpdateVideoCommandHandlerXUnitTests
{
private readonly IMapper mapper;
private readonly Mock<UnitOfWork> mockUnitOfWork;
private readonly Mock<IEmailService> emailService;
private readonly Mock<ILogger<UpdateVideoCommandHandler>> logger;
public UpdateVideoCommandHandlerXUnitTests()
{
mockUnitOfWork = MockUnitOfWork.GetUnitOfWork();
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
emailService = new Mock<IEmailService>();
logger = new Mock<ILogger<UpdateVideoCommandHandler>>();
MockVideoRepository.AddDataVideoRepository(mockUnitOfWork.Object.StreamerDbContext);
}
[Fact]
public async Task UpdateVideoTest()
{
var handler = new UpdateVideoCommandHandler(mockUnitOfWork.Object, mapper, logger.Object);
var request = new UpdateVideoCommand()
{
Id = Guid.Parse("edfe00d5-7599-4788-b52a-acc2a683b188"),
Nombre = "Alex Video 2"
};
Domain.Video result = await handler.Handle(request, CancellationToken.None);
if(result == null)
{
throw new Exception("Result is null");
}
result.ShouldBeOfType<Domain.Video>();
result.Nombre.ShouldBe("Alex Video 2");
}
}
}

View File

@@ -3,6 +3,7 @@ using CleanArchitecture.Application.Contracts.Persistence;
using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList;
using CleanArchitecture.Application.Mappings;
using CleanArchitecture.Application.UnitTests.Mocks;
using CleanArchitecture.Infrastructure.Repositories;
using Moq;
using Shouldly;
using Xunit;
@@ -13,7 +14,7 @@ namespace CleanArchitecture.Application.UnitTests.Features.Video.Queries
{
private readonly IMapper mapper;
private readonly Mock<IUnitOfWork> mockUnitOfWork;
private Mock<UnitOfWork> mockUnitOfWork;
public GetVideosListQueryHandlerXUnitTests()
{
@@ -23,7 +24,7 @@ namespace CleanArchitecture.Application.UnitTests.Features.Video.Queries
cfg.AddProfile<MappingProfile>();
});
mapper = mapperConfiguration.CreateMapper();
MockVideoRepository.AddDataVideoRepository(mockUnitOfWork.Object.StreamerDbContext);
}
[Fact]
@@ -35,8 +36,6 @@ namespace CleanArchitecture.Application.UnitTests.Features.Video.Queries
result.ShouldBeOfType<List<VideosVm>>();
result.Count.ShouldBe(1);
}
}
}

View File

@@ -1,4 +1,7 @@
using System;
using AutoFixture;
using CleanArchitecture.Domain;
using CleanArchitecture.Infrastructure.Persistence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,7 +9,23 @@ using System.Threading.Tasks;
namespace CleanArchitecture.Application.UnitTests.Mocks
{
internal class MockStreamerRepository
public static class MockStreamerRepository
{
public static void AddDataStreamerRepository(StreamerDbContext streamerDbContextFake)
{
var fixture = new Fixture();
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
var streamers = fixture.CreateMany<Streamer>().ToList();
var guidFromText = Guid.Parse("edfe00d5-7599-4788-b52a-acc2a683b188");
streamers.Add(fixture.Build<Streamer>()
.With(tr => tr.Id, guidFromText)
.Without(tr => tr.Videos)
.Create()
);
streamerDbContextFake.Streamers!.AddRange(streamers);
streamerDbContextFake.SaveChanges();
}
}
}

View File

@@ -1,18 +1,29 @@
using CleanArchitecture.Application.Contracts.Persistence;

using CleanArchitecture.Infrastructure.Persistence;
using CleanArchitecture.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace CleanArchitecture.Application.UnitTests.Mocks
{
public static class MockUnitOfWork
{
public static Mock<IUnitOfWork> GetUnitOfWork()
public static Mock<UnitOfWork> GetUnitOfWork()
{
var mockUnitOfWork = new Mock<IUnitOfWork>();
var mockVideoRepository = MockVideoRepository.GetVideoRepository();
mockUnitOfWork.Setup(uow => uow.VideoRepository).Returns(mockVideoRepository.Object);
DbContextOptions<StreamerDbContext> options;
//#if DEBUG
//#else
options = new DbContextOptionsBuilder<StreamerDbContext>()
.UseInMemoryDatabase(databaseName: $"StreamerDbContext-{Guid.NewGuid()}").Options;
//#endif
var stramerDbContextFake = new StreamerDbContext(options);
stramerDbContextFake.Database.EnsureDeleted();
var mockUnitOfWork = new Mock<UnitOfWork>(stramerDbContextFake);
return mockUnitOfWork;
}
}
}

View File

@@ -10,24 +10,22 @@ namespace CleanArchitecture.Application.UnitTests.Mocks
{
public static class MockVideoRepository
{
public static Mock<VideoRepository> GetVideoRepository()
public static void AddDataVideoRepository(StreamerDbContext streamerDbContextFake)
{
var fixture = new Fixture();
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
var videos = fixture.CreateMany<Video>().ToList();
videos.Add(fixture.Build<Video>().With(tr => tr.CreatedBy, "Alex").Create());
var options = new DbContextOptionsBuilder<StreamerDbContext>()
.UseInMemoryDatabase(databaseName: $"StreamerDbContext-{Guid.NewGuid()}").Options;
var streamerDbContextFake = new StreamerDbContext(options);
videos.Add(fixture.Build<Video>()
.With(tr => tr.CreatedBy, "Alex")
.Create()
);
videos.Add(fixture.Build<Video>()
.With(tr => tr.Id, Guid.Parse("edfe00d5-7599-4788-b52a-acc2a683b188"))
.Create()
);
streamerDbContextFake.Videos!.AddRange(videos);
streamerDbContextFake.SaveChanges();
var mockRepository = new Mock<VideoRepository>(streamerDbContextFake);
return mockRepository;
}
}
}

View File

@@ -8,14 +8,14 @@ using Microsoft.Extensions.Logging;
namespace CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer
{
public class CreateVideoCommandHandler(IUnitOfWork _unitOfWork, IMapper _mapper,
IEmailService _emailService, ILogger<CreateVideoCommandHandler> _logger) :
public class CreateStreamerCommandHandler(IUnitOfWork _unitOfWork, IMapper _mapper,
IEmailService _emailService, ILogger<CreateStreamerCommandHandler> _logger) :
IRequestHandler<CreateStreamerCommand, Streamer>
{
private readonly IUnitOfWork unitOfWork = _unitOfWork;
private readonly IMapper mapper = _mapper;
private readonly IEmailService emailService = _emailService;
private readonly ILogger<CreateVideoCommandHandler> logger = _logger;
private readonly ILogger<CreateStreamerCommandHandler> logger = _logger;
public async Task<Streamer> Handle(CreateStreamerCommand request, CancellationToken cancellationToken)
{

View File

@@ -22,7 +22,7 @@ namespace CleanArchitecture.Infrastructure.Repositories
context = _context;
}
public StreamerDbContext StreamerDbContext => context;
public async Task<int> Complete()
{

View File

@@ -8,7 +8,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets>

View File

@@ -23,7 +23,9 @@ namespace CleanArchitecture.Identity
options.UseMySql(dbConnectionString, ServerVersion.AutoDetect(dbConnectionString),
b => b.MigrationsAssembly(typeof(CleanArchitectureIdentityDbContext).Assembly.FullName)));
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<CleanArchitectureIdentityDbContext>().AddDefaultTokenProviders();
services.AddIdentity<ApplicationUser, IdentityRole>().
AddEntityFrameworkStores<CleanArchitectureIdentityDbContext>().
AddDefaultTokenProviders();
services.AddTransient<IAuthService, AuthService>();