Database forgery (EF + xUnit)
Video

A database is an external resource and we, while testing of our application, have to exclude any reference to using of that resource while lauching tests. The Entity Framework, in terms of providing work with a database, should be marked as a thing that doesn't need to be tested. If you feel like you don't sure does it work or not and have a irresistable wish to test... I you you would leave such ideas, at least, for a period of working with a commercial application)

That is why our task, while writing tests for classes, that work with a database, is to fake the database. And the Core gives us fantastically convenient instruments for that. Our implementation, for today, is going to be based on an MVC project (I hope that any other project wouldn't be a problem after understanding of a general approach).

The main idea here is to create a virtual database in the RAM, just at the moment of launching of tests.

For implementation of the following approach it is need to install the appropriate libraries.

Setting up the project under test

Probably, for most of readers, the first step is obvious, but nevertheless I'm going to show it, just to save integrity of the narrative... In the Startup.cs file of our application we should write a link to the context and the connection string.

public void ConfigureServices(IServiceCollection services)
 {
    ...
    services.AddDbContext<DataContext>(options =>
    {
        options.UseMySQL(Configuration.GetConnectionString("DataContext"));
    });
    ...
 }

Here we connect to a MySql database, but you can choose UseSqlServe or UseSqlite or whatever you want - the idea won't change.

Let me show an example of implementation of a DbContext, working with only one table named 'Article'.

public class Article
 {
    public Article() {}
    
    public Article(string name) 
    {
        Name = name;
    }
  
    public int Id { get; set; }
    public string Name { get; set; }
 }
import Microsoft.EntityFrameworkCore;

public class DataContext : DbContext
 {
    public DataContext(DbContextOptions<DataContext> options)
        : base(options)
    { }
  
    public DbSet<Article> Articles { get; set; }
 }

And the last part of the functional code, that describes a class, requesting the database.

public class ArticleRepository : IArticleRepository
 {
    private readonly DataContext _context;
    
    public ArticleRepository(DataContext context)
    {
       _context = context;
    }
    
    public async Task<bool> Delete(int id)
    {
       var entity = await _context.Articles.FirstOrDefaultAsync(e => e.Id == id);
       if (entity == null) return false;
       _context.Articles.Remove(entity);
       await _context.SaveChangesAsync();
       return true;
    }
 }

Let's finish here with the functional code and move to the main theme - the testing project.

Taking care of isolation

The 'out of the box' implementation of faking the database doesn't turn out to be a 'silver bullet' and without additional setting breaks one of the main principles of testing - the isolation.

Launching a separate test, we want it to work with it's own fake database, not noised by other tests. But to make it act this way we need to work a bit...

There are different ways of implementing of such a functionality in terms of organization of classes, you can choose whatever you want. I prefer to go the easiest way and implement a static factory in the testing project... who is without sin, let him be the first to throw a keyboard at me)

In fact, I think this approach is more than adequate - there is no reason to make fancy castles when a clay shack can give us all we need. So...

public static class ContextFactory
 {
     private static DbContextOptions<DataContext&amp;> CreateNewContextOptions()
     {
         var serviceProvider = new ServiceCollection()
             .AddEntityFrameworkInMemoryDatabase().BuildServiceProvider();
         var builder = new DbContextOptionsBuilder<DataContext&amp;>();
         builder.UseInMemoryDatabase("db", new InMemoryDatabaseRoot())
             .UseInternalServiceProvider(serviceProvider);
         return builder.Options;
     }

     public static DataContext GetContext()
     {
         return new DataContext(CreateNewContextOptions());
     }
 }
Testing

That is it, all preparations are done, programming is getting easier year by year) We can write tests. We made only one method and have no options what to test, let's check it.

public class ArticleRepositoryTests
 {
    private readonly DataContext _context;
    private readonly ArticleRepository _repository;
    
    public ArticleRepositoryTests()
    {
        _context = ContextFactory.GetContext();
        _repository = new ArticleRepository(_context);
    }
    
    [Fact]
    public async Task Delete()
    {
       var article = new Article("Some name");
       _context.Articles.Add(article);
       _context.SaveChanges();
       //
       await _repository.Delete(article.Id);
       //
       var expectedNull = _context.Articles.FirstOrDefault(e => e.Id == article.Id);
       Assert.Null(expectedNull);
    }
 }

As we can see, in test classes we can work with the context in the same way, we do it in functional code, there are no 'magic technics' here. Every time the test launches a new virtual database is being created. The database is clean and has no data (besides data that the test put into it). The isolation is done and no one from outside can break the test.