Skip to content

Commit 7a29f0b

Browse files
authored
Merge pull request #399 from nanotaboada/refactor/rename-test-methods
refactor(tests): rename test methods to Microsoft naming standard
2 parents c727528 + 71b10d7 commit 7a29f0b

6 files changed

Lines changed: 182 additions & 56 deletions

File tree

.github/copilot-instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ test/Dotnet.Samples.AspNetCore.WebApi.Tests/
4646
- **Reads**: Use `AsNoTracking()` for all EF Core read queries
4747
- **Errors**: RFC 7807 Problem Details for all error responses
4848
- **Logging**: Structured logging via `ILogger<T>`; never `Console.Write`
49-
- **Tests**: xUnit + Moq + FluentAssertions; test naming mirrors method under test
49+
- **Tests**: xUnit + Moq + FluentAssertions; naming convention per layer:
50+
- Controller: `{HttpMethod}_{Resource}_{Condition}_Returns{Outcome}` (e.g. `Get_Players_Existing_ReturnsPlayers`)
51+
- Service / Validator: `{MethodName}_{StateUnderTest}_{ExpectedBehavior}` (e.g. `RetrieveAsync_CacheMiss_QueriesRepositoryAndCachesResult`)
5052
- **Avoid**: synchronous EF Core APIs, controller business logic, static service/repository classes
5153

5254
## Commands

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,18 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc
4646

4747
### Changed
4848

49+
- Rename test methods to follow Microsoft .NET naming standard (#396)
50+
4951
### Deprecated
5052

5153
### Removed
5254

5355
### Fixed
5456

57+
- Fix broken 201 assertion in controller test for `Post_Players_NonExisting` (#396)
58+
- Add missing edge case tests for `UpdateAsync`, `DeleteAsync`, and
59+
`DateOfBirth` boundary validation (#396)
60+
5561
### Security
5662

5763
---

src/Dotnet.Samples.AspNetCore.WebApi/Validators/PlayerRequestModelValidator.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ public class PlayerRequestModelValidator : AbstractValidator<PlayerRequestModel>
1818
{
1919
private readonly IPlayerRepository _playerRepository;
2020

21-
public PlayerRequestModelValidator(IPlayerRepository playerRepository)
21+
public PlayerRequestModelValidator(
22+
IPlayerRepository playerRepository,
23+
TimeProvider? timeProvider = null
24+
)
2225
{
2326
_playerRepository = playerRepository;
27+
var clock = timeProvider ?? TimeProvider.System;
2428

2529
RuleFor(player => player.FirstName).NotEmpty().WithMessage("FirstName is required.");
2630

@@ -45,7 +49,7 @@ public PlayerRequestModelValidator(IPlayerRepository playerRepository)
4549
() =>
4650
{
4751
RuleFor(player => player.DateOfBirth)
48-
.Must(date => date!.Value.Date < DateTime.UtcNow.Date)
52+
.Must(date => date!.Value.Date < clock.GetUtcNow().Date)
4953
.WithMessage("DateOfBirth must be a date in the past.")
5054
.Must(date =>
5155
date!.Value.Date >= new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)

test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public PlayerControllerTests()
2424

2525
[Fact]
2626
[Trait("Category", "Unit")]
27-
public async Task GivenPostAsync_WhenValidatorReturnsErrors_ThenResponseStatusCodeShouldBe400BadRequest()
27+
public async Task Post_Players_ValidationError_Returns400BadRequest()
2828
{
2929
// Arrange
3030
var request = PlayerFakes.MakeRequestModelForCreate();
@@ -65,7 +65,7 @@ public async Task GivenPostAsync_WhenValidatorReturnsErrors_ThenResponseStatusCo
6565

6666
[Fact]
6767
[Trait("Category", "Unit")]
68-
public async Task GivenPostAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe409Conflict()
68+
public async Task Post_Players_Existing_Returns409Conflict()
6969
{
7070
// Arrange
7171
var request = PlayerFakes.MakeRequestModelForCreate();
@@ -108,7 +108,7 @@ public async Task GivenPostAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPla
108108

109109
[Fact]
110110
[Trait("Category", "Unit")]
111-
public async Task GivenPostAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNull_ThenResponseStatusCodeShouldBe201Created()
111+
public async Task Post_Players_NonExisting_Returns201Created()
112112
{
113113
// Arrange
114114
var request = PlayerFakes.MakeRequestModelForCreate();
@@ -146,14 +146,12 @@ public async Task GivenPostAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNul
146146
),
147147
Times.Once
148148
);
149-
if (result is CreatedAtRoute<PlayerRequestModel> httpResult)
150-
{
151-
httpResult.Should().NotBeNull().And.BeOfType<Created<PlayerResponseModel>>();
152-
httpResult.StatusCode.Should().Be(StatusCodes.Status201Created);
153-
httpResult.Value.Should().BeEquivalentTo(response);
154-
httpResult.RouteName.Should().Be("GetById");
155-
httpResult.RouteValues.Should().NotBeNull().And.ContainKey("id");
156-
}
149+
var httpResult = result.Should().BeOfType<CreatedAtRoute<PlayerResponseModel>>().Subject;
150+
httpResult.StatusCode.Should().Be(StatusCodes.Status201Created);
151+
httpResult.Value.Should().BeEquivalentTo(response);
152+
httpResult.RouteName.Should().Be("RetrieveBySquadNumber");
153+
httpResult.RouteValues.Should().NotBeNull().And.ContainKey("squadNumber");
154+
httpResult.RouteValues!["squadNumber"].Should().Be(response.Dorsal);
157155
}
158156

159157
/* -------------------------------------------------------------------------
@@ -162,7 +160,7 @@ public async Task GivenPostAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNul
162160

163161
[Fact]
164162
[Trait("Category", "Unit")]
165-
public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsListOfPlayers_ThenResponseShouldBeEquivalentToListOfPlayers()
163+
public async Task Get_Players_Existing_ReturnsPlayers()
166164
{
167165
// Arrange
168166
var response = PlayerFakes.MakeResponseModelsForRetrieve();
@@ -187,7 +185,7 @@ public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsListOfPlayers_The
187185

188186
[Fact]
189187
[Trait("Category", "Unit")]
190-
public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsEmptyList_ThenResponseStatusCodeShouldBe404NotFound()
188+
public async Task Get_Players_NonExisting_Returns404NotFound()
191189
{
192190
// Arrange
193191
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
@@ -209,7 +207,7 @@ public async Task GivenGetAsync_WhenServiceRetrieveAsyncReturnsEmptyList_ThenRes
209207

210208
[Fact]
211209
[Trait("Category", "Unit")]
212-
public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
210+
public async Task Get_PlayerById_NonExisting_Returns404NotFound()
213211
{
214212
// Arrange
215213
var id = Guid.NewGuid();
@@ -234,7 +232,7 @@ public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsNull_Then
234232

235233
[Fact]
236234
[Trait("Category", "Unit")]
237-
public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe200Ok()
235+
public async Task Get_PlayerById_Existing_Returns200OK()
238236
{
239237
// Arrange
240238
var id = Guid.NewGuid();
@@ -260,7 +258,7 @@ public async Task GivenGetByIdAsync_WhenServiceRetrieveByIdAsyncReturnsPlayer_Th
260258

261259
[Fact]
262260
[Trait("Category", "Unit")]
263-
public async Task GivenGetBySquadNumberAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
261+
public async Task Get_PlayerBySquadNumber_NonExisting_Returns404NotFound()
264262
{
265263
// Arrange
266264
var squadNumber = 999;
@@ -285,7 +283,7 @@ public async Task GivenGetBySquadNumberAsync_WhenServiceRetrieveBySquadNumberAsy
285283

286284
[Fact]
287285
[Trait("Category", "Unit")]
288-
public async Task GivenGetBySquadNumberAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe200Ok()
286+
public async Task Get_PlayerBySquadNumber_Existing_Returns200OK()
289287
{
290288
// Arrange
291289
var squadNumber = 10;
@@ -317,7 +315,7 @@ public async Task GivenGetBySquadNumberAsync_WhenServiceRetrieveBySquadNumberAsy
317315

318316
[Fact]
319317
[Trait("Category", "Unit")]
320-
public async Task GivenPutAsync_WhenValidatorReturnsErrors_ThenResponseStatusCodeShouldBe400BadRequest()
318+
public async Task Put_PlayerBySquadNumber_ValidationError_Returns400BadRequest()
321319
{
322320
// Arrange
323321
var squadNumber = 20;
@@ -361,7 +359,7 @@ public async Task GivenPutAsync_WhenValidatorReturnsErrors_ThenResponseStatusCod
361359

362360
[Fact]
363361
[Trait("Category", "Unit")]
364-
public async Task GivenPutAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
362+
public async Task Put_PlayerBySquadNumber_NonExisting_Returns404NotFound()
365363
{
366364
// Arrange
367365
var squadNumber = 999;
@@ -403,7 +401,7 @@ public async Task GivenPutAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNull
403401

404402
[Fact]
405403
[Trait("Category", "Unit")]
406-
public async Task GivenPutAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe204NoContent()
404+
public async Task Put_PlayerBySquadNumber_Existing_Returns204NoContent()
407405
{
408406
// Arrange
409407
var squadNumber = 23;
@@ -446,7 +444,7 @@ public async Task GivenPutAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPlay
446444

447445
[Fact]
448446
[Trait("Category", "Unit")]
449-
public async Task GivenDeleteAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsNull_ThenResponseStatusCodeShouldBe404NotFound()
447+
public async Task Delete_PlayerBySquadNumber_NonExisting_Returns404NotFound()
450448
{
451449
// Arrange
452450
var squadNumber = 999;
@@ -472,7 +470,7 @@ public async Task GivenDeleteAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsN
472470

473471
[Fact]
474472
[Trait("Category", "Unit")]
475-
public async Task GivenDeleteAsync_WhenServiceRetrieveBySquadNumberAsyncReturnsPlayer_ThenResponseStatusCodeShouldBe204NoContent()
473+
public async Task Delete_PlayerBySquadNumber_Existing_Returns204NoContent()
476474
{
477475
// Arrange
478476
var squadNumber = 26;

test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerServiceTests.cs

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class PlayerServiceTests
1515

1616
[Fact]
1717
[Trait("Category", "Unit")]
18-
public async Task GivenCreateAsync_WhenRepositoryAddAsync_ThenAddsPlayerToRepositoryAndRemovesCache()
18+
public async Task CreateAsync_WhenCalled_AddsPlayerAndRemovesCache()
1919
{
2020
// Arrange
2121
var request = PlayerFakes.MakeRequestModelForCreate();
@@ -49,7 +49,7 @@ public async Task GivenCreateAsync_WhenRepositoryAddAsync_ThenAddsPlayerToReposi
4949

5050
[Fact]
5151
[Trait("Category", "Unit")]
52-
public async Task GivenRetrieveAsync_WhenRepositoryGetAllAsyncReturnsPlayers_ThenCacheCreateEntryAndResultShouldBeListOfPlayers()
52+
public async Task RetrieveAsync_CacheMiss_QueriesRepositoryAndCachesResult()
5353
{
5454
// Arrange
5555
var value = It.IsAny<object>();
@@ -83,7 +83,7 @@ public async Task GivenRetrieveAsync_WhenRepositoryGetAllAsyncReturnsPlayers_The
8383

8484
[Fact]
8585
[Trait("Category", "Unit")]
86-
public async Task GivenRetrieveAsync_WhenExecutedForTheSecondTime_ThenSecondExecutionTimeShouldBeLessThanFirst()
86+
public async Task RetrieveAsync_CacheHit_ReturnsBeforeQueryingRepository()
8787
{
8888
// Arrange
8989
var value = It.IsAny<object>();
@@ -121,7 +121,7 @@ public async Task GivenRetrieveAsync_WhenExecutedForTheSecondTime_ThenSecondExec
121121

122122
[Fact]
123123
[Trait("Category", "Unit")]
124-
public async Task GivenRetrieveByIdAsync_WhenRepositoryFindByIdAsyncReturnsNull_TheResultShouldBeNull()
124+
public async Task RetrieveByIdAsync_PlayerNotFound_ReturnsNull()
125125
{
126126
// Arrange
127127
var id = Guid.NewGuid();
@@ -147,7 +147,7 @@ public async Task GivenRetrieveByIdAsync_WhenRepositoryFindByIdAsyncReturnsNull_
147147

148148
[Fact]
149149
[Trait("Category", "Unit")]
150-
public async Task GivenRetrieveByIdAsync_WhenRepositoryFindByIdAsyncReturnsPlayer_TheResultShouldBePlayer()
150+
public async Task RetrieveByIdAsync_PlayerFound_ReturnsMappedResponseModel()
151151
{
152152
// Arrange
153153
var id = Guid.NewGuid();
@@ -178,7 +178,7 @@ public async Task GivenRetrieveByIdAsync_WhenRepositoryFindByIdAsyncReturnsPlaye
178178

179179
[Fact]
180180
[Trait("Category", "Unit")]
181-
public async Task GivenRetrieveBySquadNumberAsync_WhenRepositoryFindBySquadNumberAsyncReturnsNull_ThenResultShouldBeNull()
181+
public async Task RetrieveBySquadNumberAsync_PlayerNotFound_ReturnsNull()
182182
{
183183
// Arrange
184184
var squadNumber = 999;
@@ -209,7 +209,7 @@ public async Task GivenRetrieveBySquadNumberAsync_WhenRepositoryFindBySquadNumbe
209209

210210
[Fact]
211211
[Trait("Category", "Unit")]
212-
public async Task GivenRetrieveBySquadNumberAsync_WhenRepositoryFindBySquadNumberAsyncReturnsPlayer_ThenResultShouldBePlayer()
212+
public async Task RetrieveBySquadNumberAsync_PlayerFound_ReturnsMappedResponseModel()
213213
{
214214
// Arrange
215215
var squadNumber = 10;
@@ -248,7 +248,7 @@ public async Task GivenRetrieveBySquadNumberAsync_WhenRepositoryFindBySquadNumbe
248248

249249
[Fact]
250250
[Trait("Category", "Unit")]
251-
public async Task GivenUpdateAsync_WhenRepositoryFindBySquadNumberAsyncReturnsPlayer_ThenRepositoryUpdateAsyncAndCacheRemove()
251+
public async Task UpdateAsync_PlayerFound_UpdatesRepositoryAndRemovesCache()
252252
{
253253
// Arrange
254254
var squadNumber = 23;
@@ -283,13 +283,50 @@ public async Task GivenUpdateAsync_WhenRepositoryFindBySquadNumberAsyncReturnsPl
283283
);
284284
}
285285

286+
[Fact]
287+
[Trait("Category", "Unit")]
288+
public async Task UpdateAsync_PlayerNotFound_DoesNotUpdateRepository()
289+
{
290+
// Arrange
291+
var squadNumber = 999;
292+
var request = PlayerFakes.MakeRequestModelForCreate();
293+
request.SquadNumber = squadNumber;
294+
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
295+
repository
296+
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
297+
.ReturnsAsync(null as Player);
298+
299+
var service = new PlayerService(
300+
repository.Object,
301+
logger.Object,
302+
memoryCache.Object,
303+
mapper.Object,
304+
environment.Object
305+
);
306+
307+
// Act
308+
await service.UpdateAsync(request);
309+
310+
// Assert
311+
repository.Verify(
312+
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
313+
Times.Once
314+
);
315+
repository.Verify(repository => repository.UpdateAsync(It.IsAny<Player>()), Times.Never);
316+
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Never);
317+
mapper.Verify(
318+
mapper => mapper.Map(It.IsAny<PlayerRequestModel>(), It.IsAny<Player>()),
319+
Times.Never
320+
);
321+
}
322+
286323
/* -------------------------------------------------------------------------
287324
* Delete
288325
* ---------------------------------------------------------------------- */
289326

290327
[Fact]
291328
[Trait("Category", "Unit")]
292-
public async Task GivenDeleteAsync_WhenRepositoryFindBySquadNumberAsyncReturnsPlayer_ThenRepositoryDeleteAsyncAndCacheRemove()
329+
public async Task DeleteAsync_PlayerFound_RemovesFromRepositoryAndRemovesCache()
293330
{
294331
// Arrange
295332
var squadNumber = 26;
@@ -319,6 +356,37 @@ public async Task GivenDeleteAsync_WhenRepositoryFindBySquadNumberAsyncReturnsPl
319356
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Once);
320357
}
321358

359+
[Fact]
360+
[Trait("Category", "Unit")]
361+
public async Task DeleteAsync_PlayerNotFound_DoesNotRemoveFromRepository()
362+
{
363+
// Arrange
364+
var squadNumber = 999;
365+
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
366+
repository
367+
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
368+
.ReturnsAsync(null as Player);
369+
370+
var service = new PlayerService(
371+
repository.Object,
372+
logger.Object,
373+
memoryCache.Object,
374+
mapper.Object,
375+
environment.Object
376+
);
377+
378+
// Act
379+
await service.DeleteAsync(squadNumber);
380+
381+
// Assert
382+
repository.Verify(
383+
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
384+
Times.Once
385+
);
386+
repository.Verify(repository => repository.RemoveAsync(It.IsAny<Guid>()), Times.Never);
387+
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Never);
388+
}
389+
322390
private static async Task<long> ExecutionTimeAsync(Func<Task> awaitable)
323391
{
324392
var stopwatch = new Stopwatch();

0 commit comments

Comments
 (0)