An open API service indexing awesome lists of open source software.

https://github.com/verifytests/verify.entityframework

Extends Verify to allow verification of EntityFramework bits.
https://github.com/verifytests/verify.entityframework

Last synced: about 1 year ago
JSON representation

Extends Verify to allow verification of EntityFramework bits.

Awesome Lists containing this project

README

          

# Verify.EntityFramework

[![Discussions](https://img.shields.io/badge/Verify-Discussions-yellow?svg=true&label=)](https://github.com/orgs/VerifyTests/discussions)
[![Build status](https://ci.appveyor.com/api/projects/status/g6njwv0aox62atu0?svg=true)](https://ci.appveyor.com/project/SimonCropp/verify-entityframework)
[![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFramework.svg?label=Verify.EntityFramework)](https://www.nuget.org/packages/Verify.EntityFramework/)
[![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFrameworkClassic.svg?label=Verify.EntityFrameworkClassic)](https://www.nuget.org/packages/Verify.EntityFrameworkClassic/)

Extends [Verify](https://github.com/VerifyTests/Verify) to allow snapshot testing with EntityFramework.

**See [Milestones](../../milestones?state=closed) for release notes.**

## NuGet packages

* https://nuget.org/packages/Verify.EntityFramework/
* https://nuget.org/packages/Verify.EntityFrameworkClassic/

## Enable

Enable VerifyEntityFramework once at assembly load time:

### EF Core


```cs
static IModel GetDbModel()
{
var options = new DbContextOptionsBuilder();
options.UseSqlServer("fake");
using var data = new SampleDbContext(options.Options);
return data.Model;
}

[ModuleInitializer]
public static void Init()
{
var model = GetDbModel();
VerifyEntityFramework.Initialize(model);
}
```
snippet source | anchor

The `GetDbModel` pattern allows an instance of the `IModel` to be stored for use when `IgnoreNavigationProperties` is called inside tests. This is optional, and instead can be passed explicitly to `IgnoreNavigationProperties`.

### EF Classic


```cs
[ModuleInitializer]
public static void Init() =>
VerifyEntityFrameworkClassic.Initialize();
```
snippet source | anchor

## Recording

Recording allows all commands executed by EF to be captured and then (optionally) verified.

### Enable

Call `EfRecording.EnableRecording()` on `DbContextOptionsBuilder`.


```cs
var builder = new DbContextOptionsBuilder();
builder.UseSqlServer(connection);
builder.EnableRecording();
var data = new SampleDbContext(builder.Options);
```
snippet source | anchor

`EnableRecording` should only be called in the test context.

### Usage

To start recording call `EfRecording.StartRecording()`. The results will be automatically included in verified file.


```cs
var company = new Company
{
Name = "Title"
};
data.Add(company);
await data.SaveChangesAsync();

Recording.Start();

await data
.Companies
.Where(_ => _.Name == "Title")
.ToListAsync();

await Verify();
```
snippet source | anchor

Will result in the following verified file:


```txt
{
ef: {
Type: ReaderExecutedAsync,
HasTransaction: false,
Text:
select c.Id,
c.Name
from Companies as c
where c.Name = N'Title'
}
}
```
snippet source | anchor

Sql entries can be explicitly read using `EfRecording.FinishRecording`, optionally filtered, and passed to Verify:


```cs
var company = new Company
{
Name = "Title"
};
data.Add(company);
await data.SaveChangesAsync();

Recording.Start();

await data
.Companies
.Where(_ => _.Name == "Title")
.ToListAsync();

var entries = Recording.Stop();
//TODO: optionally filter the results
await Verify(
new
{
target = data.Companies.Count(),
entries
});
```
snippet source | anchor

### DbContext spanning

`StartRecording` can be called on different DbContext instances (built from the same options) and the results will be aggregated.


```cs
var builder = new DbContextOptionsBuilder();
builder.UseSqlServer(connectionString);
builder.EnableRecording();

await using var data1 = new SampleDbContext(builder.Options);
Recording.Start();
var company = new Company
{
Name = "Title"
};
data1.Add(company);
await data1.SaveChangesAsync();

await using var data2 = new SampleDbContext(builder.Options);
await data2
.Companies
.Where(_ => _.Name == "Title")
.ToListAsync();

await Verify();
```
snippet source | anchor


```txt
{
ef: [
{
Type: ReaderExecutedAsync,
HasTransaction: false,
Parameters: {
@p0 (Int32): 0,
@p1 (String): Title
},
Text:
set implicit_transactions off;

set nocount on;

insert into Companies (Id, Name)
values (@p0, @p1)
},
{
Type: ReaderExecutedAsync,
HasTransaction: false,
Text:
select c.Id,
c.Name
from Companies as c
where c.Name = N'Title'
}
]
}
```
snippet source | anchor

### Disabling Recording for an instance


```cs
var company = new Company
{
Name = "Title"
};
data.Add(company);
await data.SaveChangesAsync();

Recording.Start();

await data
.Companies
.Where(_ => _.Name == "Title")
.ToListAsync();
data.DisableRecording();
await data
.Companies
.Where(_ => _.Name == "Disabled")
.ToListAsync();

await Verify();
```
snippet source | anchor


```txt
{
ef: {
Type: ReaderExecutedAsync,
HasTransaction: false,
Text:
select c.Id,
c.Name
from Companies as c
where c.Name = N'Title'
}
}
```
snippet source | anchor

## ChangeTracking

Added, deleted, and Modified entities can be verified by performing changes on a DbContext and then verifying the instance of ChangeTracking. This approach leverages the [EntityFramework ChangeTracker](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.changetracker).

### Added entity

This test:


```cs
[Test]
public async Task Added()
{
var options = DbContextOptions();

await using var data = new SampleDbContext(options);
var company = new Company
{
Name = "company name"
};
data.Add(company);
await Verify(data.ChangeTracker);
}
```
snippet source | anchor

Will result in the following verified file:


```txt
{
Added: {
Company: {
Id: 0,
Name: company name
}
}
}
```
snippet source | anchor

### Deleted entity

This test:


```cs
[Test]
public async Task Deleted()
{
var options = DbContextOptions();

await using var data = new SampleDbContext(options);
data.Add(new Company
{
Name = "company name"
});
await data.SaveChangesAsync();

var company = data.Companies.Single();
data.Companies.Remove(company);
await Verify(data.ChangeTracker);
}
```
snippet source | anchor

Will result in the following verified file:


```txt
{
Deleted: {
Company: {
Id: 0
}
}
}
```
snippet source | anchor

### Modified entity

This test:


```cs
[Test]
public async Task Modified()
{
var options = DbContextOptions();

await using var data = new SampleDbContext(options);
var company = new Company
{
Name = "old name"
};
data.Add(company);
await data.SaveChangesAsync();

data.Companies.Single()
.Name = "new name";
await Verify(data.ChangeTracker);
}
```
snippet source | anchor

Will result in the following verified file:


```txt
{
Modified: {
Company: {
Id: 0,
Name: {
Original: old name,
Current: new name
}
}
}
}
```
snippet source | anchor

## Queryable

This test:


```cs
var queryable = data.Companies
.Where(_ => _.Name == "company name");
await Verify(queryable);
```
snippet source | anchor

Will result in the following verified files:

### EF Core

#### CoreTests.Queryable.verified.txt


```txt
[
{
Name: company name
}
]
```
snippet source | anchor

#### CoreTests.Queryable.verified.sql


```sql
SELECT [c].[Id], [c].[Name]
FROM [Companies] AS [c]
WHERE [c].[Name] = N'company name'
```
snippet source | anchor

### EF Classic

#### ClassicTests.Queryable.verified.txt


```txt
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Content] AS [Content]
FROM [dbo].[Companies] AS [Extent1]
WHERE N'value' = [Extent1].[Content]
```
snippet source | anchor

## AllData

This test:


```cs
await Verify(data.AllData())
.AddExtraSettings(
serializer =>
serializer.TypeNameHandling = TypeNameHandling.Objects);
```
snippet source | anchor

Will result in the following verified file with all data in the database:


```txt
[
{
$type: Company,
Id: 1,
Name: Company1
},
{
$type: Company,
Id: 4,
Name: Company2
},
{
$type: Company,
Id: 6,
Name: Company3
},
{
$type: Company,
Id: 7,
Name: Company4
},
{
$type: Employee,
Id: 2,
CompanyId: 1,
Name: Employee1,
Age: 25
},
{
$type: Employee,
Id: 3,
CompanyId: 1,
Name: Employee2,
Age: 31
},
{
$type: Employee,
Id: 5,
CompanyId: 4,
Name: Employee4,
Age: 34
}
]
```
snippet source | anchor

## IgnoreNavigationProperties

`IgnoreNavigationProperties` extends `SerializationSettings` to exclude all navigation properties from serialization:


```cs
[Test]
public async Task IgnoreNavigationProperties()
{
var options = DbContextOptions();

await using var data = new SampleDbContext(options);

var company = new Company
{
Name = "company"
};
var employee = new Employee
{
Name = "employee",
Company = company
};
await Verify(employee)
.IgnoreNavigationProperties();
}
```
snippet source | anchor

### Ignore globally


```cs
var options = DbContextOptions();
using var data = new SampleDbContext(options);
VerifyEntityFramework.IgnoreNavigationProperties();
```
snippet source | anchor

## WebApplicationFactory

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) an identifier must be used to be able to retrieve the recorded commands. Start by enable recording with a unique identifier, for example the test name or a GUID:


```cs
protected override void ConfigureWebHost(IWebHostBuilder webBuilder)
{
var dataBuilder = new DbContextOptionsBuilder()
.EnableRecording(name)
.UseSqlite($"Data Source={name};Mode=Memory;Cache=Shared");
webBuilder.ConfigureTestServices(
_ => _.AddScoped(
_ => dataBuilder.Options));
}
```
snippet source | anchor

Then use the same identifier for recording:


```cs
var httpClient = factory.CreateClient();

Recording.Start(testName);

var companies = await httpClient.GetFromJsonAsync("/companies");

var entries = Recording.Stop(testName);
```
snippet source | anchor

The results will not be automatically included in verified file so it will have to be verified manually:


```cs
await Verify(
new
{
target = companies!.Length,
sql = entries
});
```
snippet source | anchor

## ScrubInlineEfDateTimes

In some scenarios EntityFrmaeowrk does not parameterise DateTimes. For example when querying [temporal tables](https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables).

`ScrubInlineEfDateTimes()` is a convenience method that calls `.ScrubInlineDateTimes("yyyy-MM-ddTHH:mm:ss.fffffffZ")`.

### Static usage

```
VerifyEntityFramework.ScrubInlineEfDateTimes();
```

### Instance usage


```cs
var settings = new VerifySettings();
settings.ScrubInlineEfDateTimes();
await Verify(target, settings);
```
snippet source | anchor

### Fluent usage


```cs
await Verify(target)
.ScrubInlineEfDateTimes();
```
snippet source | anchor

## Icon

[Database](https://thenounproject.com/term/database/310841/) designed by [Creative Stall](https://thenounproject.com/creativestall/) from [The Noun Project](https://thenounproject.com).