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.
- Why
- Usage
- How this project works
- Debugging
- Design
- EntityFramework Core Usage
- EntityFramework Core NUnit Usage
- EntityFramework Core xunit.v3 Usage
- EntityFramework Core MSTest Usage
- EntityFramework Core TUnit Usage
- EntityFramework Core Migrations
- Logging
- Template Re-generation
- https://www.nuget.org/packages/Elforyn/
- https://www.nuget.org/packages/Elforyn.NUnit/
- https://www.nuget.org/packages/Elforyn.Xunit.V3/
- https://www.nuget.org/packages/Elforyn.MSTest/
- https://www.nuget.org/packages/Elforyn.TUnit/
- 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.
- SQLite and PostgreSQL do not have compatible feature sets and there are incompatibilities between their query languages.
Why not EntityFramework InMemory
- 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.
This project supports several approaches.
Interactions with PostgreSQL via Entity Framework Core.
NUnit test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.
xunit.v3 test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.
MSTest test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.
TUnit test base class wrapping Elforyn with Arrange-Act-Assert phase enforcement.
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.
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
TDbContextassembly
A delegate executed after the template database has been created or mounted:
- Guaranteed to be called exactly once per
PgInstanceat startup - Receives a NpgsqlConnection and DbContext for seeding reference data or post-creation setup
- Called regardless of whether
buildTemplateran (useful for setup that must always occur)
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
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
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.
Robot designed by Creaticca Creative Agency from The Noun Project.