From c2164baa6290325dc07d35a9944253619a6d685d Mon Sep 17 00:00:00 2001 From: Kyle McMaster Date: Mon, 13 Nov 2023 09:35:56 -0500 Subject: [PATCH 1/8] Update README.md (#628) Update README to reflect changes from Moq to NSubstitute --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0180b4eab..34adac25a 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ Test projects could be organized based on the kind of test (unit, functional, in - [xunit](https://www.nuget.org/packages/xunit) I'm using xunit because that's what ASP.NET Core uses internally to test the product. It works great and as new versions of ASP.NET Core ship, I'm confident it will continue to work well with it. -- [Moq](https://www.nuget.org/packages/Moq/) I'm using Moq as a mocking framework for white box behavior-based tests. If I have a method that, under certain circumstances, should perform an action that isn't evident from the object's observable state, mocks provide a way to test that. I could also use my own Fake implementation, but that requires a lot more typing and files. Moq is great once you get the hang of it, and assuming you don't have to mock the world (which we don't in this case because of good, modular design). +- [NSubstitute](https://www.nuget.org/packages/NSubstitute) I'm using NSubstitute as a mocking framework for white box behavior-based tests. If I have a method that, under certain circumstances, should perform an action that isn't evident from the object's observable state, mocks provide a way to test that. I could also use my own Fake implementation, but that requires a lot more typing and files. NSubstitute is great once you get the hang of it, and assuming you don't have to mock the world (which we don't in this case because of good, modular design). - [Microsoft.AspNetCore.TestHost](https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost) I'm using TestHost to test my web project using its full stack, not just unit testing action methods. Using TestHost, you make actual HttpClient requests without going over the wire (so no firewall or port configuration issues). Tests run in memory and are very fast, and requests exercise the full MVC stack, including routing, model binding, model validation, filters, etc. From 6bddfbd0534699aae91fa702b071e818a34361a9 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Wed, 15 Nov 2023 08:23:51 -0500 Subject: [PATCH 2/8] vs code files --- .vscode/launch.json | 35 +++++++++++++++++++++++++++++++++++ .vscode/tasks.json | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..71f442675 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/Clean.Architecture.Web/bin/Debug/net7.0/Clean.Architecture.Web.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Clean.Architecture.Web", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..af4d75a71 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Clean.Architecture.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Clean.Architecture.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Clean.Architecture.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file From 57bd8542f73173da0261fe8e1179b8a85d96e23b Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 11:03:35 -0500 Subject: [PATCH 3/8] clean up see database --- Directory.Packages.props | 2 +- .../Clean.Architecture.Web.csproj | 4 +-- src/Clean.Architecture.Web/Program.cs | 29 +++++++++---------- .../Clean.Architecture.FunctionalTests.csproj | 3 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 150481538..5304b45e9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,7 +28,7 @@ - + diff --git a/src/Clean.Architecture.Web/Clean.Architecture.Web.csproj b/src/Clean.Architecture.Web/Clean.Architecture.Web.csproj index 8ee945903..d43ad69ae 100644 --- a/src/Clean.Architecture.Web/Clean.Architecture.Web.csproj +++ b/src/Clean.Architecture.Web/Clean.Architecture.Web.csproj @@ -15,9 +15,9 @@ - + - + diff --git a/src/Clean.Architecture.Web/Program.cs b/src/Clean.Architecture.Web/Program.cs index 2405c80b4..6d66c69d2 100644 --- a/src/Clean.Architecture.Web/Program.cs +++ b/src/Clean.Architecture.Web/Program.cs @@ -70,23 +70,20 @@ static void SeedDatabase(WebApplication app) { - // Seed Database - using (var scope = app.Services.CreateScope()) + using var scope = app.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + var context = services.GetRequiredService(); + // context.Database.Migrate(); + context.Database.EnsureCreated(); + SeedData.Initialize(services); + } + catch (Exception ex) { - var services = scope.ServiceProvider; - - try - { - var context = services.GetRequiredService(); - // context.Database.Migrate(); - context.Database.EnsureCreated(); - SeedData.Initialize(services); - } - catch (Exception ex) - { - var logger = services.GetRequiredService>(); - logger.LogError(ex, "An error occurred seeding the DB. {exceptionMessage}", ex.Message); - } + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred seeding the DB. {exceptionMessage}", ex.Message); } } diff --git a/tests/Clean.Architecture.FunctionalTests/Clean.Architecture.FunctionalTests.csproj b/tests/Clean.Architecture.FunctionalTests/Clean.Architecture.FunctionalTests.csproj index 3e1e75a86..54f7c23db 100644 --- a/tests/Clean.Architecture.FunctionalTests/Clean.Architecture.FunctionalTests.csproj +++ b/tests/Clean.Architecture.FunctionalTests/Clean.Architecture.FunctionalTests.csproj @@ -26,8 +26,9 @@ + + - From a36aa91db92e35ae97642823c598a4a1d9505356 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 11:11:03 -0500 Subject: [PATCH 4/8] Update default dev option to use real query --- .../AutofacInfrastructureModule.cs | 13 ++++++------- .../Queries/FakeListContributorsQueryService.cs | 5 ++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Clean.Architecture.Infrastructure/AutofacInfrastructureModule.cs b/src/Clean.Architecture.Infrastructure/AutofacInfrastructureModule.cs index 9f109cb1d..699a9651e 100644 --- a/src/Clean.Architecture.Infrastructure/AutofacInfrastructureModule.cs +++ b/src/Clean.Architecture.Infrastructure/AutofacInfrastructureModule.cs @@ -75,6 +75,9 @@ private void RegisterEF(ContainerBuilder builder) private void RegisterQueries(ContainerBuilder builder) { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); } private void RegisterMediatR(ContainerBuilder builder) @@ -117,9 +120,9 @@ private void RegisterDevelopmentOnlyDependencies(ContainerBuilder builder) builder.RegisterType().As() .InstancePerLifetimeScope(); - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); + //builder.RegisterType() + // .As() + // .InstancePerLifetimeScope(); } private void RegisterProductionOnlyDependencies(ContainerBuilder builder) @@ -127,9 +130,5 @@ private void RegisterProductionOnlyDependencies(ContainerBuilder builder) // NOTE: Add any production only (real) services here builder.RegisterType().As() .InstancePerLifetimeScope(); - - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); } } diff --git a/src/Clean.Architecture.Infrastructure/Data/Queries/FakeListContributorsQueryService.cs b/src/Clean.Architecture.Infrastructure/Data/Queries/FakeListContributorsQueryService.cs index 6ee9902f0..f1da19cc5 100644 --- a/src/Clean.Architecture.Infrastructure/Data/Queries/FakeListContributorsQueryService.cs +++ b/src/Clean.Architecture.Infrastructure/Data/Queries/FakeListContributorsQueryService.cs @@ -7,7 +7,10 @@ public class FakeListContributorsQueryService : IListContributorsQueryService { public Task> ListAsync() { - var result = new List() { new ContributorDTO(1, "Ardalis"), new ContributorDTO(2, "Snowfrog") }; + List result = + [new ContributorDTO(1, "Fake Contributor 1"), + new ContributorDTO(2, "Fake Contributor 2")]; + return Task.FromResult(result.AsEnumerable()); } } From c7de314638a4ae40a506071bfaea7661c2020bb6 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 11:44:20 -0500 Subject: [PATCH 5/8] add api.http file --- src/Clean.Architecture.Web/api.http | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/Clean.Architecture.Web/api.http diff --git a/src/Clean.Architecture.Web/api.http b/src/Clean.Architecture.Web/api.http new file mode 100644 index 000000000..57af82261 --- /dev/null +++ b/src/Clean.Architecture.Web/api.http @@ -0,0 +1,42 @@ +# For more info on HTTP files go to https://aka.ms/vs/httpfile +@hostname=localhost +@port=57679 + +// List all contributors +GET http://{{hostname}}:{{port}}/Contributors + +### + +// Get a specific contributor +@id_to_get=1 +GET http://{{hostname}}:{{port}}/Contributors/{{id_to_get}} + +### + +// Add a new contributor +POST http://{{hostname}}:{{port}}/Contributors +Content-Type: application/json + +{ + "name": "John Doe", + "email": "test@test.com" +} + +### + +// Update a contributor +@id_to_update=1 +PUT http://{{hostname}}:{{port}}/Contributors/{{id_to_update}} +Content-Type: application/json + +{ + "id": {{id_to_update}}, + "name": "ardalis2" +} + +### + +// Delete a contributor +@id_to_delete=1 +DELETE http://{{hostname}}:{{port}}/Contributors/{{id_to_delete}} + From fbb84e7c7d3cc1d858205b9602114fb39ba07fe4 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 11:49:47 -0500 Subject: [PATCH 6/8] Update sample to build with net8 Add api.http to sample --- sample/Directory.Build.props | 2 +- .../NimblePros.SampleToDo.UseCases.csproj | 1 - sample/src/NimblePros.SampleToDo.Web/api.http | 42 +++++++++++++++++++ ...mblePros.SampleToDo.FunctionalTests.csproj | 1 - ...blePros.SampleToDo.IntegrationTests.csproj | 1 - .../NimblePros.SampleToDo.UnitTests.csproj | 1 - 6 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 sample/src/NimblePros.SampleToDo.Web/api.http diff --git a/sample/Directory.Build.props b/sample/Directory.Build.props index cf4a1907f..6b47e5cab 100644 --- a/sample/Directory.Build.props +++ b/sample/Directory.Build.props @@ -2,7 +2,7 @@ true true - net7.0 + net8.0 1591 diff --git a/sample/src/NimblePros.SampleToDo.UseCases/NimblePros.SampleToDo.UseCases.csproj b/sample/src/NimblePros.SampleToDo.UseCases/NimblePros.SampleToDo.UseCases.csproj index baefd4249..4caa26845 100644 --- a/sample/src/NimblePros.SampleToDo.UseCases/NimblePros.SampleToDo.UseCases.csproj +++ b/sample/src/NimblePros.SampleToDo.UseCases/NimblePros.SampleToDo.UseCases.csproj @@ -1,7 +1,6 @@  - net7.0 enable enable diff --git a/sample/src/NimblePros.SampleToDo.Web/api.http b/sample/src/NimblePros.SampleToDo.Web/api.http new file mode 100644 index 000000000..57af82261 --- /dev/null +++ b/sample/src/NimblePros.SampleToDo.Web/api.http @@ -0,0 +1,42 @@ +# For more info on HTTP files go to https://aka.ms/vs/httpfile +@hostname=localhost +@port=57679 + +// List all contributors +GET http://{{hostname}}:{{port}}/Contributors + +### + +// Get a specific contributor +@id_to_get=1 +GET http://{{hostname}}:{{port}}/Contributors/{{id_to_get}} + +### + +// Add a new contributor +POST http://{{hostname}}:{{port}}/Contributors +Content-Type: application/json + +{ + "name": "John Doe", + "email": "test@test.com" +} + +### + +// Update a contributor +@id_to_update=1 +PUT http://{{hostname}}:{{port}}/Contributors/{{id_to_update}} +Content-Type: application/json + +{ + "id": {{id_to_update}}, + "name": "ardalis2" +} + +### + +// Delete a contributor +@id_to_delete=1 +DELETE http://{{hostname}}:{{port}}/Contributors/{{id_to_delete}} + diff --git a/sample/tests/NimblePros.SampleToDo.FunctionalTests/NimblePros.SampleToDo.FunctionalTests.csproj b/sample/tests/NimblePros.SampleToDo.FunctionalTests/NimblePros.SampleToDo.FunctionalTests.csproj index a072da669..ddec4bd59 100644 --- a/sample/tests/NimblePros.SampleToDo.FunctionalTests/NimblePros.SampleToDo.FunctionalTests.csproj +++ b/sample/tests/NimblePros.SampleToDo.FunctionalTests/NimblePros.SampleToDo.FunctionalTests.csproj @@ -2,7 +2,6 @@ - net7.0 enable enable diff --git a/sample/tests/NimblePros.SampleToDo.IntegrationTests/NimblePros.SampleToDo.IntegrationTests.csproj b/sample/tests/NimblePros.SampleToDo.IntegrationTests/NimblePros.SampleToDo.IntegrationTests.csproj index a4856e6ad..4e41820c7 100644 --- a/sample/tests/NimblePros.SampleToDo.IntegrationTests/NimblePros.SampleToDo.IntegrationTests.csproj +++ b/sample/tests/NimblePros.SampleToDo.IntegrationTests/NimblePros.SampleToDo.IntegrationTests.csproj @@ -2,7 +2,6 @@ - net7.0 enable enable diff --git a/sample/tests/NimblePros.SampleToDo.UnitTests/NimblePros.SampleToDo.UnitTests.csproj b/sample/tests/NimblePros.SampleToDo.UnitTests/NimblePros.SampleToDo.UnitTests.csproj index 3a2fafe5d..b30de0adc 100644 --- a/sample/tests/NimblePros.SampleToDo.UnitTests/NimblePros.SampleToDo.UnitTests.csproj +++ b/sample/tests/NimblePros.SampleToDo.UnitTests/NimblePros.SampleToDo.UnitTests.csproj @@ -2,7 +2,6 @@ - net7.0 true enable enable From 9d01fe1af7e2b4d4c827594839c1b3ae9e2c7604 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 12:02:25 -0500 Subject: [PATCH 7/8] Updating http file with project endpoints --- .../ListProjectsShallowQueryService.cs | 2 +- sample/src/NimblePros.SampleToDo.Web/api.http | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs index 239375136..e2d2fd6a6 100644 --- a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs +++ b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs @@ -15,7 +15,7 @@ public ListProjectsShallowQueryService(AppDbContext db) public async Task> ListAsync() { - var result = await _db.Projects.FromSqlRaw("SELECT Id, Name, Status FROM Projects") // don't fetch other big columns + var result = await _db.Projects.FromSqlRaw("SELECT Id, Name FROM Projects") // don't fetch other big columns .Select(x => new ProjectDTO(x.Id, x.Name, x.Status.ToString())) .ToListAsync(); diff --git a/sample/src/NimblePros.SampleToDo.Web/api.http b/sample/src/NimblePros.SampleToDo.Web/api.http index 57af82261..60f99b3c5 100644 --- a/sample/src/NimblePros.SampleToDo.Web/api.http +++ b/sample/src/NimblePros.SampleToDo.Web/api.http @@ -1,6 +1,6 @@ # For more info on HTTP files go to https://aka.ms/vs/httpfile @hostname=localhost -@port=57679 +@port=57678 // List all contributors GET http://{{hostname}}:{{port}}/Contributors @@ -18,8 +18,7 @@ POST http://{{hostname}}:{{port}}/Contributors Content-Type: application/json { - "name": "John Doe", - "email": "test@test.com" + "name": "John Doe" } ### @@ -40,3 +39,23 @@ Content-Type: application/json @id_to_delete=1 DELETE http://{{hostname}}:{{port}}/Contributors/{{id_to_delete}} +### + +// List all Projects +GET http://{{hostname}}:{{port}}/Projects + +### + +// Get a specific project +@id_to_get=1 +GET http://{{hostname}}:{{port}}/Projects/{{id_to_get}} + +### + +// Create a new project +POST http://{{hostname}}:{{port}}/Projects +Content-Type: application/json + +{ + "name": "New Project" +} From 762eb2ecb65a1026ca5de10d36ba534a1b354f8a Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Thu, 16 Nov 2023 12:03:49 -0500 Subject: [PATCH 8/8] Minor C# 12 updates --- .../AutofacInfrastructureModule.cs | 4 ++-- .../Data/Queries/ListProjectsShallowQueryService.cs | 10 +++------- .../Projects/ListShallow/ListProjectsShallowHandler.cs | 10 +++------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/AutofacInfrastructureModule.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/AutofacInfrastructureModule.cs index 84e203f0b..e32a53b1b 100644 --- a/sample/src/NimblePros.SampleToDo.Infrastructure/AutofacInfrastructureModule.cs +++ b/sample/src/NimblePros.SampleToDo.Infrastructure/AutofacInfrastructureModule.cs @@ -23,7 +23,7 @@ namespace NimblePros.SampleToDo.Infrastructure; public class AutofacInfrastructureModule : Module { private readonly bool _isDevelopment = false; - private readonly List _assemblies = new List(); + private readonly List _assemblies = []; public AutofacInfrastructureModule(bool isDevelopment, Assembly? callingAssembly = null) { @@ -102,7 +102,7 @@ private void RegisterMediatR(ContainerBuilder builder) foreach (var mediatrOpenType in mediatrOpenTypes) { builder - .RegisterAssemblyTypes(_assemblies.ToArray()) + .RegisterAssemblyTypes([.. _assemblies]) .AsClosedTypesOf(mediatrOpenType) .AsImplementedInterfaces(); } diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs index e2d2fd6a6..d23743c69 100644 --- a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs +++ b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListProjectsShallowQueryService.cs @@ -4,14 +4,10 @@ namespace NimblePros.SampleToDo.Infrastructure.Data.Queries; -public class ListProjectsShallowQueryService : IListProjectsShallowQueryService +public class ListProjectsShallowQueryService(AppDbContext db) : + IListProjectsShallowQueryService { - private readonly AppDbContext _db; - - public ListProjectsShallowQueryService(AppDbContext db) - { - _db = db; - } + private readonly AppDbContext _db = db; public async Task> ListAsync() { diff --git a/sample/src/NimblePros.SampleToDo.UseCases/Projects/ListShallow/ListProjectsShallowHandler.cs b/sample/src/NimblePros.SampleToDo.UseCases/Projects/ListShallow/ListProjectsShallowHandler.cs index 462354234..ad5047c08 100644 --- a/sample/src/NimblePros.SampleToDo.UseCases/Projects/ListShallow/ListProjectsShallowHandler.cs +++ b/sample/src/NimblePros.SampleToDo.UseCases/Projects/ListShallow/ListProjectsShallowHandler.cs @@ -3,14 +3,10 @@ namespace NimblePros.SampleToDo.UseCases.Projects.ListShallow; -public class ListProjectsShallowHandler : IQueryHandler>> +public class ListProjectsShallowHandler(IListProjectsShallowQueryService query) + : IQueryHandler>> { - private readonly IListProjectsShallowQueryService _query; - - public ListProjectsShallowHandler(IListProjectsShallowQueryService query) - { - _query = query; - } + private readonly IListProjectsShallowQueryService _query = query; public async Task>> Handle(ListProjectsShallowQuery request, CancellationToken cancellationToken) {