1

Is there any something like IDbContext interface or I have to write my own with every possible DbContext method?

I want to create two different contexts, where 2nd one I will use in tests

But do I actually have to redefine every real's Context method?

public interface IContext
{
    DbSet<Car> Cars { get; set; }
    DbSet<Person> Persons { get; set; }

    int SaveChanges();

    Task<int> SaveChangesAsync(CancellationToken token);
    Task<int> SaveChangesAsync(bool test, CancellationToken token);

    EntityEntry<T> Add<T>(T t) where T : class;

    (...) and much, much more?
}

public class DatabaseContext : IContext // also maybe IdentityDbContext<User>
{
    public Context(DbContextOptions<Context> options) : base(options)
    {

    }
}

public class FakeDatabase : IContext
{
    DbSet<Car> Cars { get; set; }

    public int SaveChanges()
    {
        return 0;
    }
}
Joelty
  • 1,751
  • 5
  • 22
  • 64
  • Seems to be a duplicate: https://stackoverflow.com/questions/12263514/why-dbcontext-doesnt-implement-idbcontext-interface You might want to use this `IDbContext` implementation: https://www.johanbostrom.se/blog/using-idbcontext-and-moq4-to-ease-di-and-test-your-entity-framework-context – Tim Schmelter Nov 29 '18 at 14:39
  • @Rango I've seen that, but there's no such a thing named ``IDbContext`` and that github repo does not exists – Joelty Nov 29 '18 at 14:44
  • that's why the guy in the second link has created this interface himself. Use that – Tim Schmelter Nov 29 '18 at 14:50

1 Answers1

2

As far as I know, there is no such interface. Even if there was, it would be quite some work to implement the interface.

The reason that you ask for an IContext interface, is not because you like interfaces, but you want to create a Facade: users of your interface will think they are dealing with the real database, while in fact they will be dealing with your Fake database, probably fr a unit test.

It is quite some work to implement all functionality of class DbContext, and more importantly of class DbSet<...>, especially if you want to use items as DbContext.ChangeTracker and DbContext.Database.

If you want a quick and easy method to test your original almost unchanged code, I'd go for a nuget package that represents an in-memory database, like Nuget Effort

This way, no one, not even entity framework, will know that you will be using a fake database.

class MyDbContext : DbContext
{
     // probably already existing constructors:
     public MyDbContext() : base (...) { }
     public MyDbContext(string nameOrConectionString : base (nameOrConnectionString) { }

     // extra constructor for Effort, uses existing base constructor
     public MyDbContext(DbConnection existingConnection) : base(existingConnection) { }

     ... // DbSets etc
}

Usage:

const string unitTestDbName = "Db for Unit Tests";
DbConnection inMemoryDbConnection = Effort.DbConnectionFactory.CreatePersistent(testDbName);

using (var dbContext = new MyDbContext(inMemoryDbConnection))
{
     // fill the database with test data that your test code expects
     ...
     dbContext.SaveChanges();
}

This way, nothing of your original code will be adapted for your unit tests. Using it is fairly simple, especially if you are familiar with standard usage of entity framework

However, if you think that you can trust entity framework, and you want to test your code without using entity framework, you can create an adapter for your database.

The nice thing, is that if in future you decide not to use entity framework anymore, or maybe not even a database, but some other archiving method, your code will still work. Disadvantage: you can't use the dark corners of entity framework anymore.

The idea is to create an Archive Interface and an Archive Class. The standard Archive class has a DbContext.

public interface IArchive : IDisposable
{
    IQueryable<School> Schools {get; set;}
    IQueryable<Teacher> Teachers {get; set;}
    IQueryable<Student> Students {get; set;}
}

public class Archive : IArchive, IDisposable
{
     private readonly DbContext = new SchoolDbContext(...);

     public IQueryable<School> Schools => this.DbContext.Schools;
     public IQueryable<Teacher> Teachers => this.DbContext.Teachers;
     public IQueryable<Student> Students => this.DbContext.Students;

     public int SaveChanges()
     {
         return this.DbContext.SaveChanges();
     }

     // TODO: Dispose disposes the DbContext
}

You create a special Archive for your tests:

class TestArchive : IArchive
{
     private readonly List<School> schools = new List<School>();
     private readonly List<Student> students = new List<Student>();
     ...

     public IQueryable<Student> Students => this.students.AsQueryable();
     ...         

     public int SaveChanges() {return 1;}
}

Somewhere you'll have to tell your code to instantiate your fake Archive, instead of your real Archive. This is done using the factory pattern:

interface IArchiveFactory
{
     IArchive Create();
}

class ArchiveFactory : IArchiveFactory
{
    public IArchive Create()
    {
        return new MyDbContext(...);
    }
}

class TestArchiveFactory : IArchivedFactory
{
    public IArchive TestData {get; set;}

    public IArchive Create()
    {
         return this.TestData;
    }
}

This causes quite some changes in your code: instead of just calling new MyDbContext, you'll have to ask the ArchiveFactory to create one. Furthermore, every class that wants to use new MyDbContext will need to have the same ArchiveFactory.

However, although this solution seems quite neat, it is quite a lot of work to implement, and even after you've done that it will still have limited functionality, especially if you want to Add / Remove items and want to use relationships between tables.

Every change in the relations between the tables in your database will also cause a change in your Archive. I'd go for a fake database like effort, and use the original entity framework functionality.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116