Skip to content

Commit 1b439a5

Browse files
Add support for WebApplicationFactory (#465)
1 parent 7015342 commit 1b439a5

12 files changed

Lines changed: 296 additions & 22 deletions

readme.md

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Enable VerifyEntityFramework once at assembly load time:
2626
```cs
2727
VerifyEntityFramework.Enable();
2828
```
29-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L370-L374' title='Snippet source file'>snippet source</a> | <a href='#snippet-enablecore' title='Start of snippet'>anchor</a></sup>
29+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L472-L476' title='Snippet source file'>snippet source</a> | <a href='#snippet-enablecore' title='Start of snippet'>anchor</a></sup>
3030
<!-- endSnippet -->
3131

3232

@@ -58,7 +58,7 @@ builder.UseSqlServer(connection);
5858
builder.EnableRecording();
5959
var data = new SampleDbContext(builder.Options);
6060
```
61-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L230-L237' title='Snippet source file'>snippet source</a> | <a href='#snippet-enablerecording' title='Start of snippet'>anchor</a></sup>
61+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L239-L246' title='Snippet source file'>snippet source</a> | <a href='#snippet-enablerecording' title='Start of snippet'>anchor</a></sup>
6262
<!-- endSnippet -->
6363

6464
`EnableRecording` should only be called in the test context.
@@ -86,7 +86,7 @@ await data.Companies
8686

8787
await Verify(data.Companies.Count());
8888
```
89-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L310-L327' title='Snippet source file'>snippet source</a> | <a href='#snippet-recording' title='Start of snippet'>anchor</a></sup>
89+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L319-L336' title='Snippet source file'>snippet source</a> | <a href='#snippet-recording' title='Start of snippet'>anchor</a></sup>
9090
<!-- endSnippet -->
9191

9292
Will result in the following verified file:
@@ -143,7 +143,7 @@ await Verify(new
143143
sql = entries
144144
});
145145
```
146-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L336-L359' title='Snippet source file'>snippet source</a> | <a href='#snippet-recordingspecific' title='Start of snippet'>anchor</a></sup>
146+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L438-L461' title='Snippet source file'>snippet source</a> | <a href='#snippet-recordingspecific' title='Start of snippet'>anchor</a></sup>
147147
<!-- endSnippet -->
148148

149149

@@ -174,7 +174,7 @@ await data2.Companies
174174

175175
await Verify(data2.Companies.Count());
176176
```
177-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L279-L301' title='Snippet source file'>snippet source</a> | <a href='#snippet-multidbcontexts' title='Start of snippet'>anchor</a></sup>
177+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L288-L310' title='Snippet source file'>snippet source</a> | <a href='#snippet-multidbcontexts' title='Start of snippet'>anchor</a></sup>
178178
<!-- endSnippet -->
179179

180180
<!-- snippet: CoreTests.MultiDbContexts.verified.txt -->
@@ -241,7 +241,7 @@ public async Task Added()
241241
await Verify(data.ChangeTracker);
242242
}
243243
```
244-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L8-L24' title='Snippet source file'>snippet source</a> | <a href='#snippet-added' title='Start of snippet'>anchor</a></sup>
244+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L17-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-added' title='Start of snippet'>anchor</a></sup>
245245
<!-- endSnippet -->
246246

247247
Will result in the following verified file:
@@ -275,15 +275,15 @@ public async Task Deleted()
275275
var options = DbContextOptions();
276276

277277
await using var data = new SampleDbContext(options);
278-
data.Add(new Company {Content = "before"});
278+
data.Add(new Company { Content = "before" });
279279
await data.SaveChangesAsync();
280280

281281
var company = data.Companies.Single();
282282
data.Companies.Remove(company);
283283
await Verify(data.ChangeTracker);
284284
}
285285
```
286-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L26-L42' title='Snippet source file'>snippet source</a> | <a href='#snippet-deleted' title='Start of snippet'>anchor</a></sup>
286+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L35-L51' title='Snippet source file'>snippet source</a> | <a href='#snippet-deleted' title='Start of snippet'>anchor</a></sup>
287287
<!-- endSnippet -->
288288

289289
Will result in the following verified file:
@@ -327,7 +327,7 @@ public async Task Modified()
327327
await Verify(data.ChangeTracker);
328328
}
329329
```
330-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L44-L63' title='Snippet source file'>snippet source</a> | <a href='#snippet-modified' title='Start of snippet'>anchor</a></sup>
330+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L53-L72' title='Snippet source file'>snippet source</a> | <a href='#snippet-modified' title='Start of snippet'>anchor</a></sup>
331331
<!-- endSnippet -->
332332

333333
Will result in the following verified file:
@@ -362,7 +362,7 @@ var queryable = data.Companies
362362
.Where(x => x.Content == "value");
363363
await Verify(queryable);
364364
```
365-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L209-L215' title='Snippet source file'>snippet source</a> | <a href='#snippet-queryable' title='Start of snippet'>anchor</a></sup>
365+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L218-L224' title='Snippet source file'>snippet source</a> | <a href='#snippet-queryable' title='Start of snippet'>anchor</a></sup>
366366
<!-- endSnippet -->
367367

368368
Will result in the following verified file:
@@ -410,7 +410,7 @@ await Verify(data.AllData())
410410
serializer =>
411411
serializer.TypeNameHandling = TypeNameHandling.Objects));
412412
```
413-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L190-L199' title='Snippet source file'>snippet source</a> | <a href='#snippet-alldata' title='Start of snippet'>anchor</a></sup>
413+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L199-L208' title='Snippet source file'>snippet source</a> | <a href='#snippet-alldata' title='Start of snippet'>anchor</a></sup>
414414
<!-- endSnippet -->
415415

416416
Will result in the following verified file with all data in the database:
@@ -494,9 +494,60 @@ public async Task IgnoreNavigationProperties()
494494
x => x.IgnoreNavigationProperties(data));
495495
}
496496
```
497-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L65-L88' title='Snippet source file'>snippet source</a> | <a href='#snippet-ignorenavigationproperties' title='Start of snippet'>anchor</a></sup>
497+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L74-L97' title='Snippet source file'>snippet source</a> | <a href='#snippet-ignorenavigationproperties' title='Start of snippet'>anchor</a></sup>
498498
<!-- endSnippet -->
499499

500+
## WebApplicationFactory
501+
502+
To be able to use [WebApplicationFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1) for [integration testing](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests)
503+
an identifier must be used to be able to retrive the recorded commands. Start by enable recording with a unique identifier, for example the test name or a GUID:
504+
505+
<!-- snippet: EnableRecordingWithIdentifier -->
506+
<a id='snippet-enablerecordingwithidentifier'></a>
507+
```cs
508+
.ConfigureTestServices(services =>
509+
{
510+
services.AddScoped<DbContextOptions<SampleDbContext>>(_ =>
511+
{
512+
return new DbContextOptionsBuilder<SampleDbContext>()
513+
.EnableRecording(testName)
514+
.UseSqlite($"Data Source={testName};Mode=Memory;Cache=Shared")
515+
.Options;
516+
});
517+
});
518+
```
519+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L394-L405' title='Snippet source file'>snippet source</a> | <a href='#snippet-enablerecordingwithidentifier' title='Start of snippet'>anchor</a></sup>
520+
<!-- endSnippet -->
521+
522+
Then use the same identifer for recording:
523+
524+
<!-- snippet: RecordWithIdentifier -->
525+
<a id='snippet-recordwithidentifier'></a>
526+
```cs
527+
var httpClient = factory.CreateClient();
528+
529+
EfRecording.StartRecording(testName);
530+
531+
var companies = await httpClient.GetFromJsonAsync<Company[]>("/companies");
532+
533+
var entries = EfRecording.FinishRecording(testName);
534+
```
535+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L364-L372' title='Snippet source file'>snippet source</a> | <a href='#snippet-recordwithidentifier' title='Start of snippet'>anchor</a></sup>
536+
<!-- endSnippet -->
537+
538+
The results will not be automatically included in verified file so it will have to be verified manually:
539+
540+
<!-- snippet: VerifyRecordedCommandsWithIdentifier -->
541+
<a id='snippet-verifyrecordedcommandswithidentifier'></a>
542+
```cs
543+
await Verify(new
544+
{
545+
target = companies!.Length,
546+
sql = entries
547+
});
548+
```
549+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L374-L380' title='Snippet source file'>snippet source</a> | <a href='#snippet-verifyrecordedcommandswithidentifier' title='Start of snippet'>anchor</a></sup>
550+
<!-- endSnippet -->
500551

501552
## Icon
502553

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
target: 1,
3+
sql: [
4+
{
5+
Type: ReaderExecutedAsync,
6+
Text:
7+
SELECT "c"."Id", "c"."Content"
8+
FROM "Companies" AS "c"
9+
}
10+
]
11+
}

src/Verify.EntityFramework.Tests/CoreTests.cs

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
using Microsoft.EntityFrameworkCore;
1+
using System.Net.Http.Json;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.AspNetCore.Mvc.Testing;
5+
using Microsoft.AspNetCore.TestHost;
6+
using Microsoft.Data.Sqlite;
7+
using Microsoft.EntityFrameworkCore;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
210
using Newtonsoft.Json;
311
using VerifyTests.EntityFramework;
412

513
[TestFixture]
14+
[Parallelizable(ParallelScope.All)]
615
public class CoreTests
716
{
817
#region Added
@@ -31,7 +40,7 @@ public async Task Deleted()
3140
var options = DbContextOptions();
3241

3342
await using var data = new SampleDbContext(options);
34-
data.Add(new Company {Content = "before"});
43+
data.Add(new Company { Content = "before" });
3544
await data.SaveChangesAsync();
3645

3746
var company = data.Companies.Single();
@@ -158,7 +167,7 @@ public Task ShouldIgnoreDbContext() =>
158167
Factory = new SampleDbContext(new DbContextOptions<SampleDbContext>())
159168
});
160169

161-
class MyDbContextFactory: IDbContextFactory<SampleDbContext>
170+
class MyDbContextFactory : IDbContextFactory<SampleDbContext>
162171
{
163172
public SampleDbContext CreateDbContext() =>
164173
throw new NotImplementedException();
@@ -222,7 +231,7 @@ public async Task NestedQueryable()
222231
var data = database.Context;
223232
var queryable = data.Companies
224233
.Where(x => x.Content == "value");
225-
await Verify(new {queryable});
234+
await Verify(new { queryable });
226235
}
227236

228237
void Build(string connection)
@@ -327,6 +336,99 @@ await data.Companies
327336
#endregion
328337
}
329338

339+
[DatapointSource]
340+
public IEnumerable<int> runs = Enumerable.Range(0, 5);
341+
342+
[Theory]
343+
public async Task RecordingWebApplicationFactory(int run)
344+
{
345+
// Not actually the test name, the variable name is for README.md to make sense
346+
var testName = nameof(RecordingWebApplicationFactory) + run;
347+
348+
using var connection = new SqliteConnection($"Data Source={testName};Mode=Memory;Cache=Shared");
349+
await connection.OpenAsync();
350+
351+
var factory = new CustomWebApplicationFactory(testName);
352+
353+
using (var scope = factory.Services.CreateScope())
354+
{
355+
var context = scope.ServiceProvider.GetRequiredService<SampleDbContext>();
356+
357+
await context.Database.EnsureCreatedAsync();
358+
359+
context.Add(new Company { Id = 1, Content = "Foo" });
360+
361+
await context.SaveChangesAsync();
362+
}
363+
364+
#region RecordWithIdentifier
365+
var httpClient = factory.CreateClient();
366+
367+
EfRecording.StartRecording(testName);
368+
369+
var companies = await httpClient.GetFromJsonAsync<Company[]>("/companies");
370+
371+
var entries = EfRecording.FinishRecording(testName);
372+
#endregion
373+
374+
#region VerifyRecordedCommandsWithIdentifier
375+
await Verify(new
376+
{
377+
target = companies!.Length,
378+
sql = entries
379+
});
380+
#endregion
381+
}
382+
383+
class CustomWebApplicationFactory : WebApplicationFactory<Startup>
384+
{
385+
readonly string testName;
386+
387+
public CustomWebApplicationFactory(string testName)
388+
{
389+
this.testName = testName;
390+
}
391+
392+
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
393+
builder
394+
#region EnableRecordingWithIdentifier
395+
.ConfigureTestServices(services =>
396+
{
397+
services.AddScoped<DbContextOptions<SampleDbContext>>(_ =>
398+
{
399+
return new DbContextOptionsBuilder<SampleDbContext>()
400+
.EnableRecording(testName)
401+
.UseSqlite($"Data Source={testName};Mode=Memory;Cache=Shared")
402+
.Options;
403+
});
404+
});
405+
#endregion
406+
407+
protected override IHostBuilder CreateHostBuilder() =>
408+
Host.CreateDefaultBuilder()
409+
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
410+
}
411+
412+
public class Startup
413+
{
414+
public void ConfigureServices(IServiceCollection services)
415+
{
416+
services
417+
.AddDbContext<SampleDbContext>(builder =>
418+
{
419+
builder.UseInMemoryDatabase("");
420+
});
421+
}
422+
423+
public void Configure(IApplicationBuilder app)
424+
{
425+
app.UseRouting();
426+
427+
app.UseEndpoints(endpoints
428+
=> endpoints.MapGet("/companies", async (SampleDbContext data) => await data.Companies.ToListAsync()));
429+
}
430+
}
431+
330432
[Test]
331433
public async Task RecordingSpecific()
332434
{

0 commit comments

Comments
 (0)