Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(tests)!: add cases for HttpPost, HttpPut and HttpDelete methods #63

Merged
merged 2 commits into from
Apr 17, 2024
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
230 changes: 214 additions & 16 deletions Dotnet.Samples.AspNetCore.WebApi.Tests/PlayerControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,103 @@
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;

namespace Dotnet.Samples.AspNetCore.WebApi.Tests;

public class PlayerControllerTests
{
/* -------------------------------------------------------------------------
* HTTP POST
* ---------------------------------------------------------------------- */

[Fact]
[Trait("Category", "PostAsync")]
public async Task GivenPostAsync_WhenModelStateIsInvalid_ThenResponseStatusCodeShouldBe400BadRequest()
{
// Arrange
var service = new Mock<IPlayerService>();
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);
controller.ModelState.Merge(PlayerStubs.CreateModelError("FirstName", "Required"));

// Act
var result = await controller.PostAsync(It.IsAny<Player>());

// Assert
result.Should().BeOfType<BadRequest>();
var response = result as BadRequest;
response?.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
}

[Fact]
[Trait("Category", "PostAsync")]
public async Task GivenPostAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe409Conflict()
{
// Arrange
var player = PlayerDataBuilder.SeedOneById(10);
var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>())).ReturnsAsync(player);
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.PostAsync(player);

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
result.Should().BeOfType<Conflict>();
var response = result as Conflict;
response?.StatusCode.Should().Be(StatusCodes.Status409Conflict);
}

[Fact]
[Trait("Category", "PostAsync")]
public async Task GivenPostAsync_WhenServiceRetrieveByIdAsyncReturnsNull_ThenResponseStatusCodeShouldBe201Created()
{
// Arrange
var player = PlayerDataBuilder.SeedOneById(12);

var service = new Mock<IPlayerService>();
service
.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>()))
.ReturnsAsync(null as Player);
service.Setup(service => service.CreateAsync(It.IsAny<Player>()));
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger)
{
Url = PlayerMocks.UrlHelperMock()
};

// Act
var result = await controller.PostAsync(player);

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
service.Verify(service => service.CreateAsync(It.IsAny<Player>()), Times.Exactly(1));
result.Should().BeOfType<Created<Player>>();
var response = result as Created<Player>;
response?.StatusCode.Should().Be(StatusCodes.Status201Created);
}

/* -------------------------------------------------------------------------
* HTTP GET
* ---------------------------------------------------------------------- */

[Fact]
[Trait("Category", "GetAsync")]
public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsListOfPlayers_ThenResponseShouldBeEquivalentToListOfPlayers()
{
// Arrange
var players = PlayerDataBuilder.SeedWithStarting11();

var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveAsync()).ReturnsAsync(players);
var logger = new Mock<ILogger<PlayersController>>();
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger.Object);
var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.GetAsync();
Expand All @@ -45,12 +122,11 @@ public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsEmptyList_ThenRes
{
// Arrange
var players = new List<Player>(); // Count = 0

var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveAsync()).ReturnsAsync(players);
var logger = new Mock<ILogger<PlayersController>>();
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger.Object);
var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.GetAsync();
Expand All @@ -67,13 +143,13 @@ public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsEmptyList_ThenRes
public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
{
// Arrange
var player = (Player?)null;

var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>())).ReturnsAsync(player);
var logger = new Mock<ILogger<PlayersController>>();
service
.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>()))
.ReturnsAsync(null as Player);
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger.Object);
var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.GetByIdAsync(It.IsAny<long>());
Expand All @@ -91,12 +167,11 @@ public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_Th
{
// Arrange
var player = PlayerDataBuilder.SeedOneById(10);

var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>())).ReturnsAsync(player);
var logger = new Mock<ILogger<PlayersController>>();
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger.Object);
var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.GetByIdAsync(It.IsAny<long>());
Expand All @@ -109,4 +184,127 @@ public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_Th
response?.Value.Should().BeOfType<Player>();
response?.Value.Should().BeEquivalentTo(player);
}

/* -------------------------------------------------------------------------
* HTTP PUT
* ---------------------------------------------------------------------- */

[Fact]
[Trait("Category", "PutAsync")]
public async Task GivenPutAsync_WhenModelStateIsInvalid_ThenResponseStatusCodeShouldBe400BadRequest()
{
// Arrange
var service = new Mock<IPlayerService>();
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);
controller.ModelState.Merge(PlayerStubs.CreateModelError("FirstName", "Required"));

// Act
var result = await controller.PutAsync(It.IsAny<long>(), It.IsAny<Player>());

// Assert
result.Should().BeOfType<BadRequest>();
var response = result as BadRequest;
response?.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
}

[Fact]
[Trait("Category", "PutAsync")]
public async Task GivenPutAsync_WhenServiceRetrieveByIdAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
{
// Arrange
var service = new Mock<IPlayerService>();
service
.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>()))
.ReturnsAsync(null as Player);
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.PutAsync(It.IsAny<long>(), It.IsAny<Player>());

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
result.Should().BeOfType<NotFound>();
var response = result as NotFound;
response?.StatusCode.Should().Be(StatusCodes.Status404NotFound);
}

[Fact]
[Trait("Category", "PutAsync")]
public async Task GivenPutAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe204NoContent()
{
// Arrange
var id = 10;
var player = PlayerDataBuilder.SeedOneById(id);
var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>())).ReturnsAsync(player);
service.Setup(service => service.UpdateAsync(It.IsAny<Player>()));
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.PutAsync(id, player);

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
service.Verify(service => service.UpdateAsync(It.IsAny<Player>()), Times.Exactly(1));
result.Should().BeOfType<NoContent>();
var response = result as NoContent;
response?.StatusCode.Should().Be(StatusCodes.Status204NoContent);
}

/* -------------------------------------------------------------------------
* HTTP DELETE
* ---------------------------------------------------------------------- */

[Fact]
[Trait("Category", "DeleteAsync")]
public async Task GivenDeleteAsync_WhenServiceRetrieveByIdAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
{
// Arrange
var service = new Mock<IPlayerService>();
service
.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>()))
.ReturnsAsync(null as Player);
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.DeleteAsync(It.IsAny<long>());

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
result.Should().BeOfType<NotFound>();
var response = result as NotFound;
response?.StatusCode.Should().Be(StatusCodes.Status404NotFound);
}

[Fact]
[Trait("Category", "DeleteAsync")]
public async Task GivenDeleteAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe204NoContent()
{
// Arrange
var player = PlayerDataBuilder.SeedOneById(10);
var service = new Mock<IPlayerService>();
service.Setup(service => service.RetrieveByIdAsync(It.IsAny<long>())).ReturnsAsync(player);
service.Setup(service => service.DeleteAsync(It.IsAny<long>()));
var logger = PlayerMocks.LoggerMock<PlayersController>();

var controller = new PlayersController(service.Object, logger);

// Act
var result = await controller.DeleteAsync(It.IsAny<long>());

// Assert
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<long>()), Times.Exactly(1));
service.Verify(service => service.DeleteAsync(It.IsAny<long>()), Times.Exactly(1));
result.Should().BeOfType<NoContent>();
var response = result as NoContent;
response?.StatusCode.Should().Be(StatusCodes.Status204NoContent);
}
}
16 changes: 13 additions & 3 deletions Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerMocks.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Moq;
Expand All @@ -10,23 +12,31 @@ public static ILogger<T> LoggerMock<T>()
where T : class
{
var mock = new Mock<ILogger<T>>();

return mock.Object;
}

public static IMemoryCache MemoryCacheMock(object? value)
{
var mock = new Mock<IMemoryCache>();
var fromCache = false;

var mock = new Mock<IMemoryCache>();
mock.Setup(x => x.TryGetValue(It.IsAny<object>(), out value))
.Returns(() =>
{
bool hasValue = fromCache;
fromCache = true; // Subsequent invocations will return true
return hasValue;
});

mock.Setup(x => x.CreateEntry(It.IsAny<object>())).Returns(Mock.Of<ICacheEntry>);

return mock.Object;
}

public static IUrlHelper UrlHelperMock()
{
var mock = new Mock<IUrlHelper>();
mock.Setup(u => u.Action(It.IsAny<UrlActionContext>())).Returns(It.IsAny<string>());

return mock.Object;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Data.Common;
using Dotnet.Samples.AspNetCore.WebApi.Data;
using Dotnet.Samples.AspNetCore.WebApi.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

Expand Down Expand Up @@ -56,5 +57,12 @@ public static void SeedContext(PlayerContext context)
context.AddRange(PlayerDataBuilder.SeedWithDeserializedJson());
context.SaveChanges();
}

public static ModelStateDictionary CreateModelError(string key, string errorMessage)
{
var modelStateDictionary = new ModelStateDictionary();
modelStateDictionary.AddModelError(key, errorMessage);
return modelStateDictionary;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,19 @@ public class PlayersController(IPlayerService playerService, ILogger<PlayersCont
[ProducesResponseType<Player>(StatusCodes.Status201Created)]
public async Task<IResult> PostAsync(Player player)
{
if (await _playerService.RetrieveByIdAsync(player.Id) != null)
if (!ModelState.IsValid)
{
return Results.BadRequest();
}
else if (await _playerService.RetrieveByIdAsync(player.Id) != null)
{
return Results.Conflict();
}
else
{
await _playerService.CreateAsync(player);

var location = Url.Action(nameof(PostAsync), new { id = player.Id }) ?? $"/{player.Id}";

return Results.Created(location, player);
}
}
Expand Down Expand Up @@ -85,7 +89,7 @@ public async Task<IResult> GetByIdAsync(long id)
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IResult> PutAsync(long id, Player player)
{
if (id != player.Id)
if (!ModelState.IsValid)
{
return Results.BadRequest();
}
Expand Down
Loading