Verify.EntityFramework/readme.md

13 KiB

Verify.EntityFramework

Build status NuGet Status NuGet Status

Extends Verify to allow verification of EntityFramework bits.

Support is available via a Tidelift Subscription.


Part of the .NET Foundation

Contents

NuGet package

Enable

Enable VerifyEntityFramewok 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 SqlRecording.EnableRecording() on DbContextOptionsBuilder.

var builder = new DbContextOptionsBuilder<SampleDbContext>();
builder.UseSqlServer(connection);
builder.EnableRecording();
var data = new SampleDbContext(builder.Options);

snippet source | anchor

EnableRecording should only be called in the test context.

Usage

On the DbContext call SqlRecording.StartRecording() to start recording.

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

SqlRecording.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

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 var data1 = new SampleDbContext(builder.Options);
SqlRecording.StartRecording();
var company = new Company
{
    Content = "Title"
};
data1.Add(company);
await data1.SaveChangesAsync();

await using var data2 = new SampleDbContext(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: 0,
        @p1: '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 var data = new SampleDbContext(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 var data = new SampleDbContext(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 var data = new SampleDbContext(options);
    var company = new Company
    {
        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:

var settings = new VerifySettings();
settings.ModifySerialization(
    serialization =>
        serialization.AddExtraSettings(
            serializer =>
                serializer.TypeNameHandling = TypeNameHandling.Objects));
await Verifier.Verify(data.AllData(), settings);

snippet source | anchor

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

[
  {
    $type: 'Company',
    Id: 1,
    Content: 'Company1'
  },
  {
    $type: 'Company',
    Id: 4,
    Content: 'Company2'
  },
  {
    $type: 'Company',
    Id: 6,
    Content: 'Company3'
  },
  {
    $type: 'Company',
    Id: 7,
    Content: 'Company4'
  },
  {
    $type: 'Employee',
    Id: 2,
    CompanyId: 1,
    Content: 'Employee1',
    Age: 25
  },
  {
    $type: 'Employee',
    Id: 3,
    CompanyId: 1,
    Content: 'Employee2',
    Age: 31
  },
  {
    $type: 'Employee',
    Id: 5,
    CompanyId: 4,
    Content: 'Employee4',
    Age: 34
  }
]

snippet source | anchor

Security contact information

To report a security vulnerability, use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

Icon

Database designed by Creative Stall from The Noun Project.