Skip to content

Commit

Permalink
feat(Tests): add Application integration tests and some fix and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
christianrd committed Sep 18, 2024
1 parent 6306929 commit a3b8e99
Show file tree
Hide file tree
Showing 19 changed files with 433 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
- name: Test solution
run: dotnet test --no-build --configuration Release --filter "FullyQualifiedName!~AcceptanceTests"

- name: Analyze with SonarCloud
uses: sonarsource/[email protected]
env:
SONAR_TOKEN: ${{ secrets.SONARCLOUND_TOKEN }}

- name: Publish website
if: ${{ inputs.build-artifacts == true }}
run: |
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ jobs:

- name: Autobuild
uses: github/codeql-action/autobuild@v2
env:
SkipNSwag: True

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The template includes a full CI/CD pipeline. The pipeline is responsible for bui
* [Maspter](https://github.com/MapsterMapper/Mapster)
* [FluentResult](https://github.com/altmann/FluentResults)
* [FluentValidation](https://fluentvalidation.net/)
* [NUnit](https://nunit.org/), [FluentAssertions](https://fluentassertions.com/), [NetArchTest.Rules](https://github.com/BenMorris/NetArchTest), [Moq](https://github.com/devlooped/moq) & [Respawn](https://github.com/jbogard/Respawn)
* [NUnit](https://nunit.org/), [FluentAssertions](https://fluentassertions.com/), [NetArchTest.Rules](https://github.com/BenMorris/NetArchTest), [TestContainer](https://dotnet.testcontainers.org/) & [Moq](https://github.com/devlooped/moq)

## Versions
The main branch is now on .NET 8.0.
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.dcproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>api</DockerServiceName>
<DockerComposeProjectName>FastCleanArchitecture</DockerComposeProjectName>
<DockerComposeProjectName>fast-clean-architecture</DockerComposeProjectName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3.4'

services:
web-api:
api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_HTTP_PORTS=8080
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using FastCleanArchitecture.Domain.Common;
using FastCleanArchitecture.Domain.TodoItems;
using FluentResults;
using MediatR;

namespace FastCleanArchitecture.Application.TodoItems.Commands.DeleteTodoItem;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public UpdateTodoListCommandValidator(ITodoListRepository todoListRepository)
public async Task<bool> BeUniqueTitle(UpdateTodoListCommand request, string title, CancellationToken cancellationToken)
{
var entity = await _todoListRepository.GetByIdAsync(request.Id, cancellationToken);
return entity is not null && entity!.Title!.Equals(title, StringComparison.OrdinalIgnoreCase);

return entity is null || !entity!.Title!.Equals(title, StringComparison.OrdinalIgnoreCase);
}
}
2 changes: 2 additions & 0 deletions src/Domain/TodoItems/TodoItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static TodoItem Update(string? title, bool Done, TodoItem todoItem)
{
todoItem.Title = title;
todoItem.Done = Done;
todoItem.ModifiedAtUtc = DateTime.UtcNow;
return todoItem;
}

Expand All @@ -52,6 +53,7 @@ public static TodoItem UpdateDetail(Guid listId, PriorityLevel priority, string?
todoItem.ListId = listId;
todoItem.Priority = priority;
todoItem.Note = note;
todoItem.ModifiedAtUtc = DateTime.UtcNow;
return todoItem;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FastCleanArchitecture.Infrastructure.Data;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Application.IntegrationTests.Infrastructure;
Expand Down Expand Up @@ -32,4 +33,18 @@ public async Task OneTimeTearDown()
_scope.Dispose();
DbContext.Dispose();
}

protected async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues) where TEntity : class
{
var context = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

return await context.FindAsync<TEntity>(keyValues);
}

protected async Task<int> CountAsync<TEntity>() where TEntity : class
{
var context = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

return await context.Set<TEntity>().CountAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.Common.Exceptions;
using FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Domain.TodoItems;
using FluentAssertions;

namespace Application.IntegrationTests.TodoItems.Commands;

internal sealed class CreateTodoItemTests : BaseIntegrationTest
{
[Test]
public async Task CreateTodoItem_ShouldThrowValidation_WhenRequireMinimumFields()
{
// Arrange
var command = new CreateTodoItemCommand();

// Act & Assert
await FluentActions.Invoking(() =>
Sender.Send(command)).Should().ThrowAsync<ValidationException>();
}

[Test]
public async Task CreateTodoItem_ShouldCreateTodoItem_WhenRequestIsValid()
{
// Arrange
var listId = await Sender.Send(new CreateTodoListCommand() { Title = "New List" });

var command = new CreateTodoItemCommand
{
ListId = listId.Value,
Title = "Tasks"
};

// Act
var itemId = await Sender.Send(command);

// Assert
var item = await FindAsync<TodoItem>(itemId.Value);

item.Should().NotBeNull();
item!.ListId.Should().Be(command.ListId);
item.Title.Should().Be(command.Title);
item.CreatedAtUtc.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(10000));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.ComponentModel;
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
using FastCleanArchitecture.Application.TodoItems.Commands.DeleteTodoItem;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Domain.TodoItems;
using FluentAssertions;
using FluentResults;

namespace Application.IntegrationTests.TodoItems.Commands;

internal sealed class DeleteTodoItemTests : BaseIntegrationTest
{
private Result<Guid> _listId;

[OneTimeSetUp]
public async Task SetUp() => _listId = await Sender.Send(new CreateTodoListCommand
{
Title = "New lists"
});

[TestCase(true)]
[TestCase(false)]
public async Task DeleteTodoItem_ShouldDeleteTodoItem_WhenValidParam(bool itemExists)
{
// Arrange
var itemId = itemExists ? await Sender.Send(new CreateTodoItemCommand
{
ListId = _listId.Value,
Title = "New item"
}) : Guid.NewGuid();

// Act
await Sender.Send(new DeleteTodoItemCommand(itemId.Value));

// Assert
var item = await FindAsync<TodoItem>(itemId.Value);
item.Should().BeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
using FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItemDetail;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Domain.TodoItems;
using FastCleanArchitecture.Domain.TodoItems.Enums;
using FluentAssertions;

namespace Application.IntegrationTests.TodoItems.Commands;

internal sealed class UpdateTodoItemDetailTests : BaseIntegrationTest
{
[Test]
public async Task UpdateTodoItemDetail_ShouldReturnFailure_WhenItemNotFound()
{
// Arrange
var command = new UpdateTodoItemDetailCommand
{
Id = Guid.NewGuid(),
Body = new UpdateTodoItemDetailCommand.BodyItemDetailRequest { }
};

// Act
var result = await Sender.Send(command);

// Assert
result.IsFailed.Should().BeTrue();
}

[Test]
public async Task UpdateTodoItemDetail_ShouldUpdateDetail_WhenValidParam()
{
// Arrange
var listId = await Sender.Send(new CreateTodoListCommand { Title = "New list" });
var itemId = await Sender.Send(new CreateTodoItemCommand
{
ListId = listId.Value,
Title = "New Item"
});

var command = new UpdateTodoItemDetailCommand
{
Id = itemId.Value,
Body = new UpdateTodoItemDetailCommand.BodyItemDetailRequest
{
ListId = listId.Value,
Note = "A nother note...",
Priority = PriorityLevel.High
}
};

// Act
await Sender.Send(command);

// Assert
var item = await FindAsync<TodoItem>(itemId.Value);

item.Should().NotBeNull();
item!.ListId.Should().Be(listId.Value);
item.Note.Should().Be(command.Body.Note);
item.Priority.Should().Be(command.Body.Priority);
item.ModifiedAtUtc.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(10000));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
using FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItem;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Domain.TodoItems;
using FluentAssertions;

namespace Application.IntegrationTests.TodoItems.Commands;

internal sealed class UpdateTodoItemTests : BaseIntegrationTest
{
[Test]
public async Task UpdateTodoItem_ShouldReturnFailure_WhenItemNotFound()
{
// Arrange
var command = new UpdateTodoItemCommand
{
Id = Guid.NewGuid(),
Body = new UpdateTodoItemCommand.BodyItemRequest() { Title = "New Title" }
};

// Act
var result = await Sender.Send(command);

// Assert
result.IsFailed.Should().BeTrue();
}

[Test]
public async Task UpdateTodoItem_ShouldUpdate_WhenValidParams()
{
// Arrange
var listId = await Sender.Send(new CreateTodoListCommand
{
Title = "New list",
});

var itemId = await Sender.Send(new CreateTodoItemCommand
{
ListId = listId.Value,
Title = "New item"
});

var command = new UpdateTodoItemCommand
{
Id = itemId.Value,
Body = new UpdateTodoItemCommand.BodyItemRequest { Title = "Updated item title" }
};

// Act
await Sender.Send(command);

// Assert
var item = await FindAsync<TodoItem>(itemId.Value);

item.Should().NotBeNull();
item!.Title.Should().Be(command.Body.Title);
item.ModifiedAtUtc.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(10000));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.Common.Exceptions;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
using FluentAssertions;

namespace Application.IntegrationTests.TodoLists.Commands;

internal class CreateTodoListTests : BaseIntegrationTest
{
[Test]
public async Task CreateTodoList_ShouldThrowValidationException_WhenRequireAMinimunFields()
{
// Act
var act = () => Sender.Send(new CreateTodoListCommand());

// Assert
await act.Should().ThrowAsync<ValidationException>();
}

[Test]
public async Task CreatTodoList_ShouldThrowValidationException_WhenRequireUniqueTitle()
{
// Arrange
var command = new CreateTodoListCommand { Title = "Homeworks" };
await Sender.Send(command);

// Act and Assert
await FluentActions
.Invoking(() => Sender
.Send(command))
.Should()
.ThrowAsync<ValidationException>();
}

[Test]
public async Task CreateTodoList_ShouldCreateTodoList_WhenRequestIsValidAndSuccessful()
{
// Arrange
var command = new CreateTodoListCommand { Title = "Participate in a daily meeting." };

// Act
var result = await Sender.Send(command);

// Assert
var lists = await Sender.Send(new GetTodosQuery());
var list = lists.Value.Lists.FirstOrDefault(x => x.Id!.Equals(result.Value));

result.Value.Should().NotBeEmpty();
list!.Title.Should().Be(command.Title);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Application.IntegrationTests.Infrastructure;
using FastCleanArchitecture.Application.TodoLists.Commands.Create;
using FastCleanArchitecture.Application.TodoLists.Commands.Delete;
using FastCleanArchitecture.Domain.TodoLists;
using FluentAssertions;

namespace Application.IntegrationTests.TodoLists.Commands;

internal class DeleteTodoListTests : BaseIntegrationTest
{
[Test]
public async Task DeleteTodoList_ShouldDeleteTodoList_WhenRequestIsSuccessful()
{
var listId = await Sender.Send(new CreateTodoListCommand { Title = "New list" });

// Act
await Sender.Send(new DeleteTodoListCommand(listId.Value));

// Assert
var list = await FindAsync<TodoList>(listId.Value);
list.Should().BeNull();
}
}
Loading

0 comments on commit a3b8e99

Please sign in to comment.