Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions LibraryManagement.Api.Tests.Unit/DeleteMe.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,24 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="DeepCloner" Version="0.10.4" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Tynamix.ObjectFiller" Version="1.5.9" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LibraryManagement.Api\LibraryManagement.Api.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//-----------------------------------------------------------
// Copyright (c) Coalition of Good-Hearted Engineers
// Free To Use To Build Reliable Library Management Solutions
//-----------------------------------------------------------

using EFxceptions.Models.Exceptions;
using LibraryManagement.Api.Models.Foundations.Books;
using LibraryManagement.Api.Models.Foundations.Books.Exceptions;
using Microsoft.Data.SqlClient;
using Moq;

namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Books
{
public partial class BookServiceTests
{
[Fact]
public async Task ShouldThrowCriticalDependencyExceptionOnAddIfSqlErrorOccursAndLogItAsync()
{
// given
Book someBook = CreateRandomBook();
SqlException sqlException = GetSqlError();

var failedBookStorageException =
new FailedBookStorageException(sqlException);

var expectedBookDependencyException =
new BookDependencyException(failedBookStorageException);

this.storageBrokerMock.Setup(broker =>
broker.InsertBookAsync(someBook))
.ThrowsAsync(sqlException);

// when
ValueTask<Book> addBookTask =
this.bookService.AddBookAsync(someBook);

// then
await Assert.ThrowsAsync<BookDependencyException>(() =>
addBookTask.AsTask());

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(someBook),
Times.Once);

this.loggingBrokerMock.Verify(broker =>
broker.LogCritical(It.Is(SameExceptionAs(
expectedBookDependencyException))),
Times.Once);

this.storageBrokerMock.VerifyNoOtherCalls();
this.loggingBrokerMock.VerifyNoOtherCalls();
}

[Fact]
public async Task ShouldThrowDependencyValidationOnAddIfDuplicateKeyErrorOccursAndLogItAsync()
{
// given
Book someBook = CreateRandomBook();
string someMessage = GetRandomString();

var duplicateKeyException =
new DuplicateKeyException(someMessage);

var alreadyExistsBookException =
new AlreadyExistsBookException(duplicateKeyException);

var expectedBookDependencyValidationException =
new BookDependencyValidationException(alreadyExistsBookException);

this.storageBrokerMock.Setup(broker =>
broker.InsertBookAsync(someBook))
.ThrowsAsync(duplicateKeyException);

// when
ValueTask<Book> addBookTask =
this.bookService.AddBookAsync(someBook);

// then
await Assert.ThrowsAsync<BookDependencyValidationException>(() =>
addBookTask.AsTask());

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(someBook),
Times.Once);

this.loggingBrokerMock.Verify(broker =>
broker.LogError(It.Is(SameExceptionAs(
expectedBookDependencyValidationException))),
Times.Once);

this.storageBrokerMock.VerifyNoOtherCalls();
this.loggingBrokerMock.VerifyNoOtherCalls();
}

[Fact]
public async Task ShouldThrowServiceExceptionOnAddIfServiceErrorOccursAndLogItAsync()
{
// given
Book someBook = CreateRandomBook();
var serviceException = new Exception();

var failedBookServiceException =
new FailedBookServiceException(serviceException);

var expectedBookServiceException =
new BookServiceException(failedBookServiceException);

this.storageBrokerMock.Setup(broker =>
broker.InsertBookAsync(someBook))
.ThrowsAsync(serviceException);

// when
ValueTask<Book> addBookTask =
this.bookService.AddBookAsync(someBook);

// then
await Assert.ThrowsAsync<BookServiceException>(() =>
addBookTask.AsTask());

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(someBook),
Times.Once);

this.loggingBrokerMock.Verify(broker =>
broker.LogError(It.Is(SameExceptionAs(
expectedBookServiceException))),
Times.Once);

this.storageBrokerMock.VerifyNoOtherCalls();
this.loggingBrokerMock.VerifyNoOtherCalls();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//-----------------------------------------------------------
// Copyright (c) Coalition of Good-Hearted Engineers
// Free To Use To Build Reliable Library Management Solutions
//-----------------------------------------------------------

using FluentAssertions;
using Force.DeepCloner;
using LibraryManagement.Api.Models.Foundations.Books;
using Moq;

namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Books
{
public partial class BookServiceTests
{
[Fact]
public async Task ShouldAddBookAsync()
{
// given
Book randomBook = CreateRandomBook();
Book inputBook = randomBook;
Book storageBook = inputBook;
Book expectedBook = storageBook.DeepClone();

this.storageBrokerMock.Setup(broker =>
broker.InsertBookAsync(inputBook))
.ReturnsAsync(storageBook);

// when
Book actualBook =
await this.bookService.AddBookAsync(inputBook);

// then
actualBook.Should().BeEquivalentTo(expectedBook);

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(inputBook),
Times.Once);

this.storageBrokerMock.VerifyNoOtherCalls();
this.loggingBrokerMock.VerifyNoOtherCalls();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//-----------------------------------------------------------
// Copyright (c) Coalition of Good-Hearted Engineers
// Free To Use To Build Reliable Library Management Solutions
//-----------------------------------------------------------

using LibraryManagement.Api.Models.Foundations.Books;
using LibraryManagement.Api.Models.Foundations.Books.Exceptions;
using Moq;

namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Books
{
public partial class BookServiceTests
{
[Fact]
public async Task ShouldThrowValidationExceptionOnAddIfBookIsNullAndLogItAsync()
{
// given
Book nullBook = null;
var nullBookException = new NullBookException();

var expectedBookValidationException =
new BookValidationException(nullBookException);

// when
ValueTask<Book> addBookTask =
this.bookService.AddBookAsync(nullBook);

// then
await Assert.ThrowsAsync<BookValidationException>(() =>
addBookTask.AsTask());

this.loggingBrokerMock.Verify(broker =>
broker.LogError(It.Is(SameExceptionAs(
expectedBookValidationException))),
Times.Once);

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(It.IsAny<Book>()),
Times.Never);

this.loggingBrokerMock.VerifyNoOtherCalls();
this.storageBrokerMock.VerifyNoOtherCalls();
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public async Task ShouldThrowValidationExceptionOnAddIfBookIsInvalidAndLogItAsync(
string invalidText)
{
var invalidBook = new Book
{
BookTitle = invalidText
};

var invalidBookException = new InvalidBookException();

invalidBookException.AddData(
key: nameof(Book.BookId),
values: "Id is required");

invalidBookException.AddData(
key: nameof(Book.ReaderId),
values: "Id is required");

invalidBookException.AddData(
key: nameof(Book.BookTitle),
values: "Text is required");

invalidBookException.AddData(
key: nameof(Book.Author),
values: "Text is required");

invalidBookException.AddData(
key: nameof(Book.Genre),
values: "Text is required");


var expectedBookValidationException =
new BookValidationException(invalidBookException);

// when
ValueTask<Book> addBookTask =
this.bookService.AddBookAsync(invalidBook);

// then
await Assert.ThrowsAsync<BookValidationException>(() =>
addBookTask.AsTask());

this.loggingBrokerMock.Verify(broker =>
broker.LogError(It.Is(SameExceptionAs(
expectedBookValidationException))),
Times.Once);

this.storageBrokerMock.Verify(broker =>
broker.InsertBookAsync(It.IsAny<Book>()),
Times.Never);

this.loggingBrokerMock.VerifyNoOtherCalls();
this.storageBrokerMock.VerifyNoOtherCalls();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//-----------------------------------------------------------
// Copyright (c) Coalition of Good-Hearted Engineers
// Free To Use To Build Reliable Library Management Solutions
//-----------------------------------------------------------

using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using LibraryManagement.Api.Brokers.Loggings;
using LibraryManagement.Api.Brokers.Storages;
using LibraryManagement.Api.Models.Foundations.Books;
using LibraryManagement.Api.Services.Foundations.Books;
using Microsoft.Data.SqlClient;
using Moq;
using Tynamix.ObjectFiller;
using Xeptions;

namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Books
{
public partial class BookServiceTests
{
private readonly Mock<IStorageBroker> storageBrokerMock;
private readonly Mock<ILoggingBroker> loggingBrokerMock;
private readonly IBookService bookService;

public BookServiceTests()
{
this.storageBrokerMock = new Mock<IStorageBroker>();
this.loggingBrokerMock = new Mock<ILoggingBroker>();
this.bookService = new BookService(
storageBroker: this.storageBrokerMock.Object,
loggingBroker: this.loggingBrokerMock.Object);
}

private static Book CreateRandomBook() =>
CreateBookFiller(date: GetRandomDateTimeOffset()).Create();

private static DateTimeOffset GetRandomDateTimeOffset() =>
new DateTimeRange(earliestDate: new DateTime()).GetValue();

private static SqlException GetSqlError() =>
(SqlException)RuntimeHelpers.GetUninitializedObject(typeof(SqlException));

private static string GetRandomString() =>
new MnemonicString().GetValue();

private Expression<Func<Xeption, bool>> SameExceptionAs(Xeption expectedException) =>
actualException => actualException.SameExceptionAs(expectedException);

private static Filler<Book> CreateBookFiller(DateTimeOffset date)
{
var filler = new Filler<Book>();

filler.Setup()
.OnType<DateTimeOffset>().Use(date);

return filler;
}
}
}
1 change: 1 addition & 0 deletions LibraryManagement.Api/LibraryManagement.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</PackageReference>
<PackageReference Include="RESTFulSense" Version="3.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Xeption" Version="2.8.0" />
</ItemGroup>

</Project>
Loading
Loading