Components
One of the core features of Accelergreat is the concept of components. A component represents a dependency for your tests. For example a database or a web API.
If you haven't already, please read the main documentation.
Initialization
Components are initialized at the beginning of a test assembly execution, before any tests start executing.
[assembly: UseAccelergreatXunitTestFramework]
namespace ExampleTestProject;
public class Startup : IAccelergreatStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingletonAccelergreatComponent<C1>();
services.AddAccelergreatComponent<C2>();
services.AddAccelergreatComponent<C3>();
}
}
Order is important
Components are initialized in the order they are registered within the class that implements IAccelergreatStartup.ConfigureServices
.
Component | C1 | C2 | C3 |
---|---|---|---|
Consumes | data[0], data[1] | data[2] | |
Adds | data[0], data[1] | data[2] |
Lifetime
Transient
A component is registered as transient by calling IServiceCollection.AddAccelergreatComponent
or IServiceCollection.AddTransientAccelergreatComponent
. This means 1 component instance per environment. If you have 8 logical processors and at least 8 test collections to warrant 8 parallel environments, there will be 8 instances of the component.
Singleton
A component is registered as singleton by calling IServiceCollection.AddSingletonAccelergreatComponent
. This means 1 component instance per assembly. If you have 8 logical processors and at least 8 test collections to warrant 8 parallel environments, there will be 1 instance of the component shared between 8 environments.
When a component is registered as singleton, the IAccelergreatComponent.ResetAsync
method is not called.
Pre-built components
Databases (Entity Framework)
SQL Server
Package Accelergreat.EntityFramework.SqlServer
Class SqlServerEntityFrameworkDatabaseComponent
Configuration schema see the SqlServerEntityFramework
section in schema.json
Reset strategies
- SnapshotRollback (default)
Executes
CREATE DATABASE AS SNAPSHOT OF
of the database after initialization. ExecutesRESTORE DATABASE FROM DATABASE SNAPSHOT
in between each test execution. Reset duration should be expected to be between 80ms and 150ms. - Transactions (premium feature) Starts a transaction savepoint after initialization. Rolls back to the initialization savepoint between each test execution. Reset duration should be expected to be between 0ms and 3ms.
Creation strategies
- TypeConfigurations (default)
Calls
EnsureDatabaseCreatedAsync
on theDbContext
to create the database - Migrations
Calls
MigrateAsync
on theDbContext
to create the database
PostgreSql
Package Accelergreat.EntityFramework.PostgreSql
Class PostgreSqlEntityFrameworkDatabaseComponent
Configuration schema see the PostgreSqlEntityFramework
section in schema.json
Reset strategies
- DumpRestore (default)
Calls
pg_dump
of the database after initialization. Callspg_restore
in between each test execution. - Transactions (premium feature) Starts a transaction and creates savepoint after initialization. Rolls back to the initialization savepoint between each test execution.
Creation strategies
- TypeConfigurations Calls EnsureDatabaseCreatedAsync on the DbContext to create the database
- Migrations Calls MigrateAsync on the DbContext to create the database
Sqlite
Package Accelergreat.EntityFramework.Sqlite
Class SqliteEntityFrameworkDatabaseComponent
InMemory
Package Accelergreat.EntityFramework.InMemory
Class InMemoryEntityFrameworkDatabaseComponent
GlobalData
You may hit a scenario where you want to have some global data setup at the beginning of your tests that persists throughout the entire test assembly execution.
All EntityFramework database components have a OnDatabaseInitializedAsync
method that you can override to add such data that is saved before the initial state of the database is set.
Here is an example:
namespace ExampleTestProject;
public class TestSqlServerDatabaseComponent : SqlServerEntityFrameworkDatabaseComponent<AdventureWorks2016Context>
{
public TestSqlServerDatabaseComponent ()
{
AutoRegisterGlobalDataItemsInDbContextCreation = true;
}
protected override async Task OnDatabaseInitializedAsync(AdventureWorks2016Context context)
{
await context.AddAsync(new Person());
await context.SaveChangesAsync();
}
}
Transactions (premium feature)
If you are using the transactions configuration for your database component, any dependant components / tests will need to connect to the database under the same transaction/connection.
Accelergreat has some extension methods to support this:
ServiceCollectionExtensions.AddSqlServerDbContextUsingTransaction
For SQL Server only. Removes service descriptors that EntityFramework adds when calling
AddDbContext
orAddDbContextFactory
and re-registers usingAddDbContextFactory
to use the same transaction/connection.ServiceCollectionExtensions.AddPostgreSqlDbContextUsingTransaction
For PostgreSQL only. Removes service descriptors that EntityFramework adds when calling
AddDbContext
orAddDbContextFactory
and re-registers usingAddDbContextFactory
to use the same transaction/connection.ServiceCollectionExtensions.AddAccelergreatDbContext
Universal extension that supports all database components regardless if they are configured to use transactions or not. This uses the Accelergreat DbContextFactory to create the DbContext.
For use in a WebAppComponent
or KestrelWebAppComponent
you can call these extension methods when overriding the ConfigureWebHost
method.
Transaction overriding
Transaction overriding is a an opt-in feature that is designed to support a scenario where you want to use the transactions reset strategy but your application you are testing creates transactions of its own. Which EntityFramework does not allow the creation of nested transactions.
Accelergreat has implemented overrides for EntityFramework's transaction classes. This effectively re-routes the following DbContext transaction methods:
original method | destination method |
---|---|
Commit | CreateSavepoint |
CommitAsync | CreateSavepointAsync |
Rollback | RollbackToSavepoint |
RollbackAsync | RollbackToSavepointAsync |
If this scenario suits your needs and you would like to have your cake and also be able to eat it, transaction overriding is for you.
To enable, useTransactionOverriding
is an optional parameter that can be set to true in any of the Accelergreat extension methods around adding DbContexts specified above.
Web applications
Both WebAppComponent
and KestrelWebAppComponent
extend WebApplicationFactory
. You will be able to use these just like a normal WebApplicationFactory but with added Accelergreat magic and the ability to easily use as a component.
Web application (in memory)
Package Accelergreat.Web
Class WebAppComponent
Web application (kestrel)
Package Accelergreat.Web
Class KestrelWebAppComponent
Build your own components
You will most likely hit a scenario when you want to build your own component to support functionality that Accelergreat hasn't yet covered. For example if you need to run a docker container to run the test database on.
Template
All you need to do will need to create a class that implements the IAccelergreatComponent
interface.
namespace ExampleTestProject;
public class ExampleComponent : IAccelergreatComponent
{
async Task IAccelergreatComponent.InitializeAsync(IAccelergreatEnvironmentPipelineData accelergreatEnvironmentPipelineData)
{
/*
* Initialize your component here.
* This method will be called before the tests within the test assembly have started executing.
* You can use accelergreatEnvironmentPipelineData to reference any data that a component that was initialized before this one has added.
* You can add data to accelergreatEnvironmentPipelineData for use in any dependent components that initialize after this one.
*/
}
async Task IAccelergreatComponent.ResetAsync()
{
/*
* Reset your component here.
* This method is called between each test to enable you to put the component back to a state of your choosing.
* This method will not be called if this component is registered as a singleton component.
*/
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
/*
* Dispose your component here.
* This method is called after all the test assembly has finished executing.
*/
}
}