Skip to content

SimonCropp/Elforyn

Repository files navigation

Elforyn

Build status NuGet Status NuGet Status NuGet Status NuGet Status NuGet Status

Provides a wrapper around PostgreSQL to simplify running tests against Entity Framework Core using CREATE DATABASE ... TEMPLATE for fast per-test database isolation.

See Milestones for release notes.

Contents

NuGet packages

Why

Goals

  • Have an isolated PostgreSQL Database for each unit test method.
  • Does not overly impact performance.
  • Results in a running PostgreSQL Database that can be accessed via pgAdmin (or other tooling) to diagnose issues when a test fails.

Why not SQLite

  • SQLite and PostgreSQL do not have compatible feature sets and there are incompatibilities between their query languages.
  • Difficult to debug the state. When debugging a test, or looking at the resultant state, it is helpful to be able to interrogate the Database using tooling
  • InMemory is implemented with shared mutable state between instance. This results in strange behaviors when running tests in parallel, for example when creating keys.
  • InMemory is not intended to be an alternative to a relational database, and as such it does not support the full suite of relational features.

See the official guidance: InMemory is not a relational database.

Usage

This project supports several approaches.

EntityFramework Core

Interactions with PostgreSQL via Entity Framework Core.

Full Usage

EntityFramework Core NUnit

NUnit test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.

Full Usage

EntityFramework Core xunit.v3

xunit.v3 test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.

Full Usage

EntityFramework Core MSTest

MSTest test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.

Full Usage

EntityFramework Core TUnit

TUnit test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.

Full Usage

How this project works

Inputs

buildTemplate

A delegate that builds the template database schema. Called zero or once based on the current state of the underlying PostgreSQL template:

  • Not called if a valid template already exists (timestamp matches)
  • Called once if the template needs to be created or rebuilt

The delegate receives a connected DbContext to create schema and seed initial data.

timestamp

A timestamp used to determine if the template database needs to be rebuilt:

  • If the timestamp is newer than the existing template, the template is recreated
  • Defaults to the last modified time of the TDbContext assembly

callback

A delegate executed after the template database has been created or mounted:

  • Guaranteed to be called exactly once per PgInstance at startup
  • Receives a NpgsqlConnection and DbContext for seeding reference data or post-creation setup
  • Called regardless of whether buildTemplate ran (useful for setup that must always occur)

PgInstance Startup Flow

This flow happens once per PgInstance, usually once before any tests run.

flowchart TD
    start[Start]
    openMaster[Open Master Connection]
    checkExists{Template DB<br>Exists?}
    checkTimestamp{Timestamp<br>Match?}
    checkCallback{Callback<br>Exists?}

    dropTemplate[Drop Template DB]
    createTemplateDb[Create Template DB]

    subgraph openTemplateForNewBox[Open Template Connection]
        runBuildTemplate[Run buildTemplate]
        checkCallbackAfterBuild{Callback<br>Exists?}
        runCallbackAfterBuild[Run Callback]
    end

    setTimestamp["Store Timestamp<br>(COMMENT ON DATABASE)"]
    markTemplate["Mark as Template<br>(IS_TEMPLATE true)"]

    subgraph openTemplateForExistingBox[Open Template Connection]
        runCallback[Run Callback]
    end

    done[Done]

    start --> openMaster
    openMaster --> checkExists

    checkExists -->|No| createTemplateDb

    checkExists -->|Yes| checkTimestamp

    checkTimestamp -->|Match| checkCallback
    checkTimestamp -->|Mismatch| dropTemplate
    dropTemplate --> createTemplateDb

    createTemplateDb --> runBuildTemplate
    runBuildTemplate --> checkCallbackAfterBuild
    checkCallbackAfterBuild -->|Yes| runCallbackAfterBuild
    checkCallbackAfterBuild -->|No| setTimestamp
    runCallbackAfterBuild --> setTimestamp
    setTimestamp --> markTemplate
    markTemplate --> done

    checkCallback -->|Yes| runCallback
    checkCallback -->|No| done
    runCallback --> done
Loading

Create PgDatabase Flow

This happens once per PgInstance.Build, usually once per test method.

flowchart TD
    entry[Start]

    subgraph openMaster[Open Master Connection]
        dropIfExists["DROP DATABASE IF EXISTS"]
        createFromTemplate["CREATE DATABASE ... TEMPLATE"]
    end
    openNewConn[Open New Connection]
    returnConn[Return Connection]

    entry --> dropIfExists
    dropIfExists --> createFromTemplate
    createFromTemplate --> openNewConn
    openNewConn --> returnConn
Loading

Debugging

To connect to the PostgreSQL instance use pgAdmin or any PostgreSQL client with the connection string used to initialize the PgInstance.

The connection details will be written to Trace.WriteLine when a PgInstance is constructed. See Logging.

Icon

Robot designed by Creaticca Creative Agency from The Noun Project.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages