DbTest 1.1.4

dotnet add package DbTest --version 1.1.4
                    
NuGet\Install-Package DbTest -Version 1.1.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DbTest" Version="1.1.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DbTest" Version="1.1.4" />
                    
Directory.Packages.props
<PackageReference Include="DbTest" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DbTest --version 1.1.4
                    
#r "nuget: DbTest, 1.1.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=DbTest&version=1.1.4
                    
Install DbTest as a Cake Addin
#tool nuget:?package=DbTest&version=1.1.4
                    
Install DbTest as a Cake Tool

DbTest

DbTest is a tiny test library for integration test with EntityFrameworkCore in .NET application. It helps you to write tests with a real database very easy and native. It does only dirty work with the database, and you still need NUnit, XUnit or any of your favorite test framework.

DbTest gives you clean database for each test. It offers easy and maintainable way to create initial fixtures and test cases.

Motivation

Common approach to test .NET application that work with database is to separate data access code from business logic code. This Data Access Layer will be substitute by mocks in tests. It is very power and flexible method, but it also has some disadvantages: producing many boilerplate types, complexity growth and so on. You have to separate business logic and data access even you don't want it. And the saddest thing you still need to test Data Access Layer.

There is a simpler approach which widespead in dynamic language world. Instead of create and control database abstraction, it offers use real database and control it's state. Test framework gives you clean database before each tests and you can create test case by filling it. This approach is significantly more convenient and understandable. Tests with real database are simpler and give more confidence.

Influences

DbTest is inspired by the test approach from Ruby on Rails, Django, Yii2 and many other perfect dynamic language frameworks. This approach is a very popular in dynamic languages.

What's you get

  • Clean real database Real database has many aspects that can not be emulated by Data Access Layer: constraints, triggers, complex SQL queries and so on. DbTest reset database to initial state before each test. SqlServer and Postgresql are supported now.

  • Strong type fixtures There are some libraries that can create initial state throught txt files. Any changes in project can broke those fixtures, because compiler don't process them. DbTest offer a way to describe fixtures in code. You will get autocomplete, refactoring and type checking when you write and maintain fixtures.

  • Clean and understandable tests Write your tests as a book, even non technical people can read and participate.

Installation

Install in your *.Tests project:

Install-Package DbTest

You can see usages in Examples

Fixtures

A real system has many relations between models, we need to fill many tables before create one row in a target table. For example, Products can be related to Manufacturers which related to Country.

Let's start from model which has no relations:

public class CountriesFixture : IModelFixture<Country>
{
    public static Country Scotland => new Country
    {
        Id = 1,
        Name = "Scotland",
        IsDeleted = false
    };

    public static Country USA => new Country
    {
        Id = 2,
        Name = "USA",
        IsDeleted = false
    };
}

First of all we need class that realize IModelFixture<T> interface. Each model instances are declared as static to give easy access to it from any part of other fixtures or tests. You need set identifiers explicitly and control uniqueness between one class of model.

Now, we are ready to create Manufacturers and Products.

class ManufacturersFixture : IModelFixture<Manufacturer>
{

    public static Manufacturer BrownForman => new Manufacturer
    {
        Id = 1,
        Name = "Brown-Forman",
        CountryId = CountriesFixture.USA.Id,
        IsDeleted = false
    };

    public static Manufacturer TheEdringtonGroup => new Manufacturer
    {
        Id = 2,
        Name = "The Edrington Group",
        CountryId = CountriesFixture.Scotland.Id,
        IsDeleted = false
    };
}

public class GoodsFixture : IModelFixture<Good>
{
    public static Good JackDaniels => new Good
    {
        Id = 1,
        Name = "Jack Daniels, 0.5l",
        ManufacturerId = ManufacturersFixture.BrownForman.Id,
        IsDeleted = false
    };

    public static Good FamousGrouseFinest => new Good
    {
        Id = 2,
        Name = "The Famous Grouse Finest, 0.5l",
        ManufacturerId = ManufacturersFixture.TheEdringtonGroup.Id,
        IsDeleted = false
    };
}

Pay attention to external keys in models we do not set it explicitly instead take value from another fixture. You give advise from intellisence when create your fixtures and check from compiler when your system change and fixtures must changed too.

Prepare database for test

DbSet apply migrations and clean database before every test, to prepare call this

    new EFTestDatabase<MyContext>(SandBox.GetContext());
        .ResetWithFixtures(
            new CountriesFixture(),
            new ManufacturersFixture(),
            new GoodsFixture()
        );

SandBox

SandBox is a conception of test environment. It has connection to test database and methods to clean and load initial fixtures

static class SandBox
{
    const string ConnectionString = @"User ID=test;Password=test;Host=localhost;Port=5432;Database=test;Pooling=true;";

    public static void InitDatabase()
    {        
        new EFTestDatabase<MyContext>(GetContext())
            .ResetWithFixtures(
                new CountriesFixture(),
                new ManufacturersFixture(),
                new GoodsFixture()
            );
    }

    public MyContext GetContext()
    {
        var optionsBuilder = new DbContextOptionsBuilder<StockDbContext>();
        optionsBuilder.UseNpgsql(ConnectionString);

        return new StockDbContext(optionsBuilder.Options);
    }
}

Model builder

Model builder is a helper to create test case. It is not included in DbTest, you have to create it. Each methods of this class must create and return one entity:

public class ModelBuilder
{
    public MoveDocument CreateDocument(string time, Storage source, Storage dest)
    {
        var document = new MoveDocument
        {
            Number = "#",

            SourceStorageId = source.Id,
            DestStorageId = dest.Id,

            Time = ParseTime(time),
            IsDeleted = false
        };

        using (var db = SandBox.GetContext())
        {
            db.MoveDocuments.Add(document);
            db.SaveChanges();
        }

        return document;
    }

    public MoveDocumentItem AddGood(MoveDocument document, Good good, decimal count)
    {
        var item = new MoveDocumentItem
        {
            MoveDocumentId = document.Id,
            GoodId = good.Id,
            Count = count
        };

        using (var db = SandBox.GetContext())
        {
            db.MoveDocumentItems.Add(item);
            db.SaveChanges();
        }

        return item;
    }
}

Use in test

[SetUp]
public void SetUp()
{
    World.InitDatabase(); // Main part, prepare database for test
}

[Test]
public void CalculateRemainsForMoveDocuments()
{
    // prepare test
    var builder = new ModelBuilder();           

    var doc1 = builder.CreateDocument("15.01.2016 10:00:00", Storages.MainStorage, Storages.RemoteStorage);
    builder.AddGood(doc1, Goods.JackDaniels, 10);
    builder.AddGood(doc1, Goods.FamousGrouseFinest, 15);
               
    var doc2 = builder.CreateDocument("16.01.2016 20:00:00", Storages.RemoteStorage, Storages.MainStorage);
    builder.AddGood(doc2, Goods.FamousGrouseFinest, 7);

    // test
    var remains = RemainsService.GetRemainFor(Storages.RemoteStorage, new DateTime(2016, 02, 01));

    // assert
    Assert.AreEqual(2,  remains.Count);
    Assert.AreEqual(10, remains.Single(x => x.GoodId == Goods.JackDaniels.Id).Count);
    Assert.AreEqual(8,  remains.Single(x => x.GoodId == Goods.FamousGrouseFinest.Id).Count);
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on DbTest:

Package Downloads
DbTest.EFCore

Extension for DbTest to use with Entity Framework Core

DbTest.EF6

Extension for DbTest to use with Entity Framework 6

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.4 122 10/16/2024
1.1.3 98 10/15/2024
1.1.2 93 10/15/2024
1.1.1 100 10/15/2024
1.1.0 90 10/15/2024
1.0.0 109 10/15/2024
0.2.0 1,708 3/8/2018
0.1.8 1,280 5/22/2017
0.1.7 1,079 5/22/2017
0.1.6 1,078 5/22/2017
0.1.5 1,128 1/10/2017
0.1.4 1,306 1/10/2017
0.1.3 1,190 1/2/2017
0.1.2 1,170 1/2/2017
0.1.1 1,189 1/2/2017
0.1.0 1,185 1/2/2017