Extends Verify to allow verification of EntityFramework bits.
Перейти к файлу
GitHub Action 7ed200aa35 Docs changes 2021-10-14 04:37:21 +00:00
.github GitHubSync update - master 2021-05-18 19:01:06 +10:00
src cleanup 2021-10-14 15:36:43 +11:00
.gitignore Update .gitignore 2020-10-26 08:56:07 +11:00
code_of_conduct.md . 2020-07-25 09:59:16 +10:00
license.txt Update license.txt 2020-07-25 08:59:20 +10:00
readme.md Docs changes 2021-10-14 04:37:21 +00:00

readme.md

Verify.EntityFramework

Build status NuGet Status NuGet Status

Extends Verify to allow snapshot testing with EntityFramework.


Part of the .NET Foundation

NuGet package

Enable

Enable VerifyEntityFramework once at assembly load time:

EF Core

VerifyEntityFramework.Enable();

snippet source | anchor

EF Classic

VerifyEntityFrameworkClassic.Enable();

snippet source | anchor

Recording

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

Enable

Call EfRecording.EnableRecording() on DbContextOptionsBuilder.

DbContextOptionsBuilder<SampleDbContext> builder = new();
builder.UseSqlServer(connection);
builder.EnableRecording();
SampleDbContext data = new(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.

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

EfRecording.StartRecording();

await data.Companies
    .Where(x => x.Content == "Title")
    .ToListAsync();

await Verifier.Verify(data.Companies.Count());

snippet source | anchor

Will result in the following verified file:

{
  target: 5,
  sql: [
    {
      Type: ReaderExecutedAsync,
      Text:
SELECT [c].[Id], [c].[Content]
FROM [Companies] AS [c]
WHERE [c].[Content] = N'Title'
    },
    {
      Type: ReaderExecuted,
      Text:
SELECT COUNT(*)
FROM [Companies] AS [c]
    }
  ]
}

snippet source | anchor

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

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

EfRecording.StartRecording();

await data.Companies
    .Where(x => x.Content == "Title")
    .ToListAsync();

var entries = EfRecording.FinishRecording();
//TODO: optionally filter the results
await Verifier.Verify(new
{
    target = data.Companies.Count(),
    sql = 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.

var builder = new DbContextOptionsBuilder<SampleDbContext>();
builder.UseSqlServer(connectionString);
builder.EnableRecording();

await using SampleDbContext data1 = new(builder.Options);
EfRecording.StartRecording();
var company = new Company
{
    Content = "Title"
};
data1.Add(company);
await data1.SaveChangesAsync();

await using SampleDbContext data2 = new(builder.Options);
await data2.Companies
    .Where(x => x.Content == "Title")
    .ToListAsync();

await Verifier.Verify(data2.Companies.Count());

snippet source | anchor

{
  target: 5,
  sql: [
    {
      Type: ReaderExecutedAsync,
      HasTransaction: true,
      Parameters: {
        @p0 (Int32): 0,
        @p1 (String?): Title
      },
      Text:
SET NOCOUNT ON;
INSERT INTO [Companies] ([Id], [Content])
VALUES (@p0, @p1);
    },
    {
      Type: ReaderExecutedAsync,
      Text:
SELECT [c].[Id], [c].[Content]
FROM [Companies] AS [c]
WHERE [c].[Content] = N'Title'
    },
    {
      Type: ReaderExecuted,
      Text:
SELECT COUNT(*)
FROM [Companies] AS [c]
    }
  ]
}

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.

Added entity

This test:

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

    await using SampleDbContext data = new(options);
    var company = new Company
    {
        Content = "before"
    };
    data.Add(company);
    await Verifier.Verify(data.ChangeTracker);
}

snippet source | anchor

Will result in the following verified file:

{
  Added: {
    Company: {
      Id: 0,
      Content: before
    }
  }
}

snippet source | anchor

Deleted entity

This test:

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

    await using SampleDbContext data = new(options);
    data.Add(new Company {Content = "before"});
    await data.SaveChangesAsync();

    var company = data.Companies.Single();
    data.Companies.Remove(company);
    await Verifier.Verify(data.ChangeTracker);
}

snippet source | anchor

Will result in the following verified file:

{
  Deleted: {
    Company: {
      Id: 0
    }
  }
}

snippet source | anchor

Modified entity

This test:

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

    await using SampleDbContext data = new(options);
    Company company = new()
    {
        Content = "before"
    };
    data.Add(company);
    await data.SaveChangesAsync();

    data.Companies.Single().Content = "after";
    await Verifier.Verify(data.ChangeTracker);
}

snippet source | anchor

Will result in the following verified file:

{
  Modified: {
    Company: {
      Id: 0,
      Content: {
        Original: before,
        Current: after
      }
    }
  }
}

snippet source | anchor

Queryable

This test:

var queryable = data.Companies
    .Where(x => x.Content == "value");
await Verifier.Verify(queryable);

snippet source | anchor

Will result in the following verified file:

EF Core

SELECT [c].[Id], [c].[Content]
FROM [Companies] AS [c]
WHERE [c].[Content] = N'value'

snippet source | anchor

EF Classic

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:

await Verifier.Verify(data.AllData())
    .ModifySerialization(
        serialization =>
            serialization.AddExtraSettings(
                serializer =>
                    serializer.TypeNameHandling = TypeNameHandling.Objects));

snippet source | anchor

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

[
  {
    $type: Company,
    Id: Id_1,
    Content: Company1
  },
  {
    $type: Company,
    Id: Id_2,
    Content: Company2
  },
  {
    $type: Company,
    Id: Id_3,
    Content: Company3
  },
  {
    $type: Company,
    Id: Id_4,
    Content: Company4
  },
  {
    $type: Employee,
    Id: Id_5,
    CompanyId: Id_1,
    Content: Employee1,
    Age: 25
  },
  {
    $type: Employee,
    Id: Id_6,
    CompanyId: Id_1,
    Content: Employee2,
    Age: 31
  },
  {
    $type: Employee,
    Id: Id_7,
    CompanyId: Id_2,
    Content: Employee4,
    Age: 34
  }
]

snippet source | anchor

IgnoreNavigationProperties

IgnoreNavigationProperties extends SerializationSettings to exclude all navigation properties from serialization:

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

    await using SampleDbContext data = new(options);

    var company = new Company
    {
        Content = "company"
    };
    var employee = new Employee
    {
        Content = "employee",
        Company = company
    };
    await Verifier.Verify(employee)
        .ModifySerialization(
            x => x.IgnoreNavigationProperties(data));
}

snippet source | anchor

Icon

Database designed by Creative Stall from The Noun Project.