Skip to content

Commit 48897c4

Browse files
authored
Merge pull request #463 from nanotaboada/feat/repository-integration-tests
test(repositories): add integration tests for `Repository<T>` and `PlayerRepository` (#461)
2 parents 9631511 + e463018 commit 48897c4

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc
4444

4545
### Added
4646

47+
- Add `test/.../Integration/PlayerRepositoryTests.cs` with 9 integration tests covering `Repository<T>` (`GetAllAsync`, `FindByIdAsync`, `RemoveAsync`) and `PlayerRepository` (`FindBySquadNumberAsync`, `SquadNumberExistsAsync`); all tests use `DatabaseFakes.MigrateAsync()` on in-memory SQLite and are tagged `[Trait("Category", "Integration")]` (#461)
4748
- Add `ValidateAsync_SquadNumberNegative_ReturnsValidationError` test to exercise the `GreaterThan(0)` rule with a negative value, which passes `NotEmpty()` but fails the greater-than rule (#427)
4849
- Add `ValidateAsync_FirstNameEmptyInUpdateRuleSet_ReturnsValidationError` test to verify the `"Update"` rule set enforces structural field validation (#427)
4950
- Add `adr/` directory with 12 Architecture Decision Records documenting architectural choices, technology decisions, and design trade-offs (#372)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
using System.Data.Common;
2+
using Dotnet.Samples.AspNetCore.WebApi.Data;
3+
using Dotnet.Samples.AspNetCore.WebApi.Models;
4+
using Dotnet.Samples.AspNetCore.WebApi.Repositories;
5+
using Dotnet.Samples.AspNetCore.WebApi.Tests.Utilities;
6+
using FluentAssertions;
7+
8+
namespace Dotnet.Samples.AspNetCore.WebApi.Tests.Integration;
9+
10+
/// <summary>
11+
/// Integration tests for <see cref="Repository{T}"/> and <see cref="PlayerRepository"/>.
12+
/// Each test runs against an in-memory SQLite database with the full EF Core migration
13+
/// chain applied via <see cref="DatabaseFakes.MigrateAsync"/>, which also validates
14+
/// that the migration chain itself is healthy as a side effect.
15+
/// </summary>
16+
public class PlayerRepositoryTests : IAsyncLifetime
17+
{
18+
private DbConnection _connection = default!;
19+
private PlayerDbContext _dbContext = default!;
20+
private PlayerRepository _repository = default!;
21+
22+
public async Task InitializeAsync()
23+
{
24+
var (connection, options) = DatabaseFakes.CreateSqliteConnection();
25+
_connection = connection;
26+
_dbContext = DatabaseFakes.CreateDbContext(options);
27+
await _dbContext.MigrateAsync();
28+
_repository = new PlayerRepository(_dbContext);
29+
}
30+
31+
public async Task DisposeAsync()
32+
{
33+
await _dbContext.DisposeAsync();
34+
await _connection.DisposeAsync();
35+
}
36+
37+
/* -------------------------------------------------------------------------
38+
* GetAllAsync
39+
* ---------------------------------------------------------------------- */
40+
41+
[Fact]
42+
[Trait("Category", "Integration")]
43+
public async Task GetAllAsync_WhenCalled_ReturnsAllSeededPlayers()
44+
{
45+
// Act
46+
var players = await _repository.GetAllAsync();
47+
48+
// Assert
49+
players.Should().HaveCount(26);
50+
_dbContext.ChangeTracker.Entries<Player>().Should().BeEmpty();
51+
}
52+
53+
/* -------------------------------------------------------------------------
54+
* FindByIdAsync
55+
* ---------------------------------------------------------------------- */
56+
57+
[Fact]
58+
[Trait("Category", "Integration")]
59+
public async Task FindByIdAsync_ExistingId_ReturnsPlayer()
60+
{
61+
// Arrange — resolve a real ID from the seeded database
62+
var seeded = await _repository.GetAllAsync();
63+
var existingId = seeded[0].Id;
64+
65+
// Act
66+
var player = await _repository.FindByIdAsync(existingId);
67+
68+
// Assert
69+
player.Should().NotBeNull();
70+
player!.Id.Should().Be(existingId);
71+
}
72+
73+
[Fact]
74+
[Trait("Category", "Integration")]
75+
public async Task FindByIdAsync_UnknownId_ReturnsNull()
76+
{
77+
// Act
78+
var player = await _repository.FindByIdAsync(Guid.NewGuid());
79+
80+
// Assert
81+
player.Should().BeNull();
82+
}
83+
84+
/* -------------------------------------------------------------------------
85+
* RemoveAsync
86+
* ---------------------------------------------------------------------- */
87+
88+
[Fact]
89+
[Trait("Category", "Integration")]
90+
public async Task RemoveAsync_ExistingEntity_RemovesFromDatabase()
91+
{
92+
// Arrange
93+
var seeded = await _repository.GetAllAsync();
94+
var existingId = seeded[0].Id;
95+
96+
// Act
97+
await _repository.RemoveAsync(existingId);
98+
99+
// Assert
100+
var player = await _repository.FindByIdAsync(existingId);
101+
player.Should().BeNull();
102+
}
103+
104+
[Fact]
105+
[Trait("Category", "Integration")]
106+
public async Task RemoveAsync_UnknownId_NoExceptionThrown()
107+
{
108+
// Arrange
109+
var countBefore = (await _repository.GetAllAsync()).Count;
110+
111+
// Act
112+
var act = async () => await _repository.RemoveAsync(Guid.NewGuid());
113+
114+
// Assert
115+
await act.Should().NotThrowAsync();
116+
var countAfter = (await _repository.GetAllAsync()).Count;
117+
countAfter.Should().Be(countBefore);
118+
}
119+
120+
/* -------------------------------------------------------------------------
121+
* FindBySquadNumberAsync
122+
* ---------------------------------------------------------------------- */
123+
124+
[Fact]
125+
[Trait("Category", "Integration")]
126+
public async Task FindBySquadNumberAsync_ExistingSquadNumber_ReturnsPlayer()
127+
{
128+
// Arrange
129+
var expected = PlayerFakes.MakeFromStarting11(23);
130+
131+
// Act
132+
var player = await _repository.FindBySquadNumberAsync(expected.SquadNumber);
133+
134+
// Assert
135+
player.Should().NotBeNull();
136+
player!.SquadNumber.Should().Be(expected.SquadNumber);
137+
}
138+
139+
[Fact]
140+
[Trait("Category", "Integration")]
141+
public async Task FindBySquadNumberAsync_UnknownSquadNumber_ReturnsNull()
142+
{
143+
// Arrange — derive a squad number guaranteed not to exist in the seeded data
144+
var seeded = await _repository.GetAllAsync();
145+
var unknownSquadNumber = seeded.Max(p => p.SquadNumber) + 1;
146+
147+
// Act
148+
var player = await _repository.FindBySquadNumberAsync(unknownSquadNumber);
149+
150+
// Assert
151+
player.Should().BeNull();
152+
}
153+
154+
/* -------------------------------------------------------------------------
155+
* SquadNumberExistsAsync
156+
* ---------------------------------------------------------------------- */
157+
158+
[Fact]
159+
[Trait("Category", "Integration")]
160+
public async Task SquadNumberExistsAsync_ExistingSquadNumber_ReturnsTrue()
161+
{
162+
// Arrange
163+
var expected = PlayerFakes.MakeFromStarting11(23);
164+
165+
// Act
166+
var exists = await _repository.SquadNumberExistsAsync(expected.SquadNumber);
167+
168+
// Assert
169+
exists.Should().BeTrue();
170+
}
171+
172+
[Fact]
173+
[Trait("Category", "Integration")]
174+
public async Task SquadNumberExistsAsync_UnknownSquadNumber_ReturnsFalse()
175+
{
176+
// Arrange — derive a squad number guaranteed not to exist in the seeded data
177+
var seeded = await _repository.GetAllAsync();
178+
var unknownSquadNumber = seeded.Max(p => p.SquadNumber) + 1;
179+
180+
// Act
181+
var exists = await _repository.SquadNumberExistsAsync(unknownSquadNumber);
182+
183+
// Assert
184+
exists.Should().BeFalse();
185+
}
186+
}

0 commit comments

Comments
 (0)