Creates EF Core repository/persistence integration tests with the Builder pattern and SQL Server Testcontainers. Seeding through the DbContext directly.
Installs into .claude/skills of the current project.
Are you the author of Tests Integration Tests?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/pierrebelin-tests-integration-tests)
---
name: tests-integration-tests
description: "Creates EF Core repository/persistence integration tests with the Builder pattern and SQL Server Testcontainers. Seeding through the DbContext directly."
argument-hint: "[repository or method to test]"
model: sonnet
---
# Integration tests — Builder pattern + direct DbContext
Verify persistence against real SQL Server isolated by `DatabaseContainerPool`. Builder for data, seeding through `DbContext` directly, not through the repository under test.
`references/examples.md` only when template below misses the persistence pattern at hand.
Batch supplied → read only its Tests and DDD Design sections: they decide whether this level is required.
## Strict rules
- Test covering rule of handler's rules table (`kit:rules`) carries it: `[Trait("RM", "{HandlerFolder}/{RM|RL-xx}")]` under `[Fact]`/`[Theory]`. Rule proven only here stops counting untested (`.claude/rules/tests.md`). No documented rule → no trait.
- One builder per aggregate: `With*()` for properties (chaining `this`), `As*()` for presets (`AsDraft`, `AsPublished`). `Build()` uses `Restore()`, not `Create()`.
- Seed through `DbContext` directly, not through the repository under test (circular).
- Real SQL Server Testcontainers database: reuse `BaseTestFixture` / `DatabaseContainerPool`; no SQLite in-memory, no shared database.
- Persistence check: fresh context instance or detached entities when verifying persisted state needs it.
- Independent: no shared state. Each fixture gets an isolated SQL Server database from the pool, releases it after the test.
- Naming of tests, classes → `.claude/rules/tests.md` (loads on opening a file under `tests/`).
- Scope: only when plan touches repository, EF mapping, SQL query, persistence constraint. Business rules stay in handler unit tests.
## Structure
```
tests/{{PRODUCT}}.IntegrationTests/
├── Core/
│ ├── BaseIntegrationFixture.cs (IAsyncLifetime, pool-isolated database)
│ ├── BaseTestFixture.cs (AppDbContext, UnitOfWork, services)
│ └── DataBuilder/
│ └── [Entity]EntityBuilder.cs (one per aggregate)
├── [BoundedContext]/
│ └── [Repository]/
│ └── [MethodName]/
│ ├── [MethodName]Fixture.cs (extends BaseTestFixture)
│ └── [MethodName]Tests.cs
```
Class naming → `.claude/rules/tests.md`, `Naming` table.
## Templates
### Builder
```csharp
public class [Entity]Builder
{
private [EntityId] _id = [EntityId].Create();
private string _name = "Test";
private EntityStatus _status = EntityStatus.Draft;
public static [Entity]Builder Create() => new();
public [Entity]Builder WithId([EntityId] id) { _id = id; return this; }
public [Entity]Builder WithName(string name) { _name = name; return this; }
public [Entity]Builder AsDraft() { _status = EntityStatus.Draft; return this; }
public [Entity]Builder AsPublished() { _status = EntityStatus.Published; return this; }
public [Entity] Build() => [Entity].Restore(_id, _name, _status);
}
```
### SQL Server Testcontainers fixture
```csharp
public class [MethodName]Fixture : BaseTestFixture
{
private [Repository] _repository = null!;
protected override void OnInitialized()
{
_repository = new [Repository](UnitOfWork, ChangeLogWriter);
}
public [MethodName]Fixture With[Setup]()
{
DbContext.Set<[Entity]Entity>().Add([Entity]EntityBuilder.Create().Build());
DbContext.SaveChanges();
return this;
}
public Task<[Result]> Execute() => _repository.[Method](...);
}
```
### Test class
```csharp
public class [MethodName]Tests : IAsyncLifetime
{
private readonly [MethodName]Fixture _fixture = new();
public ValueTask InitializeAsync() => _fixture.InitializeAsync();
public ValueTask DisposeAsync() => _fixture.DisposeAsync();
[Fact]
public async Task Should[Result]_When[Condition]()
{
var result = await _fixture
.With[Setup]()
.Execute();
Assert.Equal(expected, result);
}
}
```
## Coverage per repository
1. **CRUD**: Save (insert), Save (update through events), GetById, Delete
2. **Queries**: filtering, pagination, includes (navigation properties)
3. **Constraints**: unique constraints, FK enforced
4. **Domain representation**: values, relations, dates restored correctly without re-running `Create()`
## Final verification
- [ ] Target test green: the suite's test-mode variable, then `commands.test` with `{suite}` = `IntegrationTests`, narrowed to `*[MethodName]Tests` (`skills/implement-tdd/references/test-scope.md` § Runner, § 3)
- [ ] Direct `DbContext` seeding, `BaseTestFixture` / `DatabaseContainerPool` fixture
- [ ] Persisted state verified after detaching or re-reading where needed
- [ ] This test really covers persistence, not business rules