Skip to content

Commit 8741f02

Browse files
committed
docs
1 parent 261cb46 commit 8741f02

3 files changed

Lines changed: 132 additions & 1 deletion

File tree

pages/efmigrations.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,62 @@ await data.Database.MigrateAsync();
7676
<!-- endSnippet -->
7777

7878
This constructs a `DbContext` using the options builder (which is pre-configured to connect to the template database) and then applies all pending migrations. After this point the template database has the full schema and is ready to be cloned for individual tests.
79+
80+
81+
## Testing a specific migration
82+
83+
To test a single migration in isolation, build the template up to the migration _before_ the one under test using `IMigrator.MigrateAsync("targetMigration")`. Each test then receives a clone at that point and can apply the next migration independently.
84+
85+
### Build the template to a known migration
86+
87+
<!-- snippet: MigrateToSpecificTarget -->
88+
<a id='snippet-MigrateToSpecificTarget'></a>
89+
```cs
90+
static SqlInstance<MyDbContext> sqlInstance = new(
91+
buildTemplate: async (connection, options) =>
92+
{
93+
await using var data = new MyDbContext(options.Options);
94+
var migrator = data.GetInfrastructure()
95+
.GetRequiredService<IMigrator>();
96+
// apply up to and including a target migration
97+
await migrator.MigrateAsync("Migration_002_AddOrders");
98+
},
99+
constructInstance: builder => new(builder.Options));
100+
```
101+
<sup><a href='/src/EfLocalDb.Tests/Snippets/TestSpecificMigration.cs#L5-L18' title='Snippet source file'>snippet source</a> | <a href='#snippet-MigrateToSpecificTarget' title='Start of snippet'>anchor</a></sup>
102+
<!-- endSnippet -->
103+
104+
This uses `IMigrator` (resolved from the EF Core service provider) instead of `Database.MigrateAsync()` so a target migration name can be specified. The template is frozen at that point and cloned for every test.
105+
106+
### Apply and verify the next migration
107+
108+
<!-- snippet: TestSingleMigration -->
109+
<a id='snippet-TestSingleMigration'></a>
110+
```cs
111+
[Test]
112+
public async Task TestNextMigration()
113+
{
114+
await using var database = await sqlInstance.Build();
115+
116+
// apply the next migration under test
117+
var migrator = database.Context
118+
.GetInfrastructure()
119+
.GetRequiredService<IMigrator>();
120+
await migrator.MigrateAsync("Migration_003_AddOrderStatus");
121+
122+
// verify the migration applied the expected schema change
123+
await using var command = database.Connection.CreateCommand();
124+
command.CommandText = """
125+
SELECT COUNT(*)
126+
FROM INFORMATION_SCHEMA.COLUMNS
127+
WHERE TABLE_NAME = 'Orders'
128+
AND COLUMN_NAME = 'Status'
129+
""";
130+
var result = (int) (await command.ExecuteScalarAsync())!;
131+
That(result, Is.EqualTo(1));
132+
}
133+
```
134+
<sup><a href='/src/EfLocalDb.Tests/Snippets/TestSpecificMigration.cs#L20-L45' title='Snippet source file'>snippet source</a> | <a href='#snippet-TestSingleMigration' title='Start of snippet'>anchor</a></sup>
135+
<!-- endSnippet -->
136+
137+
Each test gets its own database clone at the earlier migration state. It then applies the target migration and verifies the expected schema change. Because every test starts from the same cloned template, migrations under test are fully isolated from each other.

pages/mdsource/efmigrations.source.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,21 @@ Perform a [Runtime apply of migrations](https://docs.microsoft.com/en-us/ef/core
3939

4040
snippet: Migrate
4141

42-
This constructs a `DbContext` using the options builder (which is pre-configured to connect to the template database) and then applies all pending migrations. After this point the template database has the full schema and is ready to be cloned for individual tests.
42+
This constructs a `DbContext` using the options builder (which is pre-configured to connect to the template database) and then applies all pending migrations. After this point the template database has the full schema and is ready to be cloned for individual tests.
43+
44+
45+
## Testing a specific migration
46+
47+
To test a single migration in isolation, build the template up to the migration _before_ the one under test using `IMigrator.MigrateAsync("targetMigration")`. Each test then receives a clone at that point and can apply the next migration independently.
48+
49+
### Build the template to a known migration
50+
51+
snippet: MigrateToSpecificTarget
52+
53+
This uses `IMigrator` (resolved from the EF Core service provider) instead of `Database.MigrateAsync()` so a target migration name can be specified. The template is frozen at that point and cloned for every test.
54+
55+
### Apply and verify the next migration
56+
57+
snippet: TestSingleMigration
58+
59+
Each test gets its own database clone at the earlier migration state. It then applies the target migration and verifies the expected schema change. Because every test starts from the same cloned template, migrations under test are fully isolated from each other.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// ReSharper disable UnusedParameter.Local
2+
3+
public class TestSpecificMigration
4+
{
5+
#region MigrateToSpecificTarget
6+
7+
static SqlInstance<MyDbContext> sqlInstance = new(
8+
buildTemplate: async (connection, options) =>
9+
{
10+
await using var data = new MyDbContext(options.Options);
11+
var migrator = data.GetInfrastructure()
12+
.GetRequiredService<IMigrator>();
13+
// apply up to and including a target migration
14+
await migrator.MigrateAsync("Migration_002_AddOrders");
15+
},
16+
constructInstance: builder => new(builder.Options));
17+
18+
#endregion
19+
20+
#region TestSingleMigration
21+
22+
[Test]
23+
public async Task TestNextMigration()
24+
{
25+
await using var database = await sqlInstance.Build();
26+
27+
// apply the next migration under test
28+
var migrator = database.Context
29+
.GetInfrastructure()
30+
.GetRequiredService<IMigrator>();
31+
await migrator.MigrateAsync("Migration_003_AddOrderStatus");
32+
33+
// verify the migration applied the expected schema change
34+
await using var command = database.Connection.CreateCommand();
35+
command.CommandText = """
36+
SELECT COUNT(*)
37+
FROM INFORMATION_SCHEMA.COLUMNS
38+
WHERE TABLE_NAME = 'Orders'
39+
AND COLUMN_NAME = 'Status'
40+
""";
41+
var result = (int) (await command.ExecuteScalarAsync())!;
42+
That(result, Is.EqualTo(1));
43+
}
44+
45+
#endregion
46+
47+
class MyDbContext(DbContextOptions options) :
48+
DbContext(options)
49+
{
50+
public DbSet<TheEntity> TestEntities { get; set; } = null!;
51+
52+
protected override void OnModelCreating(ModelBuilder model) =>
53+
model.Entity<TheEntity>();
54+
}
55+
}

0 commit comments

Comments
 (0)