Base class for testing Repositories


There are many ways you can write a test. Here is another way to get started quickly. It has everything you might need 🤣 Uses SQLite database, applies default migrations, has Fixture... it even mocks for us!

The example below tests a unit of work service.

 

using System.Data.Common;
using AutoFixture;
using DemoEFCoreAPI.Contexts;
using DemoEFCoreAPI.Services.Repositories;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Moq;namespace DemoEFCore.Tests.Integration;

public class BaseTest : IDisposable, IAsyncDisposable
{
    protected Fixture Fixture { get; private set; }
    protected ApplicationDbContext DbContextTest { get; private set; }
    private readonly DbConnection _connection;
    private bool _disposed;    protected Mock<ApplicationDbContext> MockDbContext { get; private set; }
    protected Mock<IUnitOfWork> MockUnitOfWork { get; private set; }
    protected Mock<IBookRepository> MockBookRepository { get; private set; }    protected BaseTest()
    {
        Fixture = new Fixture();
        
        MockDbContext = new Mock<ApplicationDbContext>();
        MockUnitOfWork = new Mock<IUnitOfWork>();
        MockBookRepository = new Mock<IBookRepository>();
        
        var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
        _connection = new SqliteConnection(connectionStringBuilder.ToString());
        _connection.Open();        var dbContextBuilder = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseSqlite(_connection)
            .EnableSensitiveDataLogging();        DbContextTest = new ApplicationDbContext(dbContextBuilder.Options);
        
        // Ensure the database is created
        DbContextTest.Database.EnsureCreated();
    }    // Dispose() method for synchronous disposal
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }    // DisposeAsync() method for asynchronous disposal
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
        Dispose(false);
        GC.SuppressFinalize(this);
    }    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;        if (disposing)
        {
            // Dispose managed resources
            DbContextTest?.Dispose();
            _connection?.Close();
        }        // Dispose unmanaged resources if any
        _disposed = true;
    }    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_disposed)
            return;        if (DbContextTest != null)
        {
            await DbContextTest.DisposeAsync();
            DbContextTest = null;
        }        if (_connection != null)
        {
            await _connection.DisposeAsync();
        }        _disposed = true;
    }

/// <summary>
/// BaseTest() is the syntax for a finalizer in C#.
/// The tilde (~) followed by the class name defines the finalizer for that class.
///
/// Finalizers are used to clean up unmanaged resources before the instance of the class
/// is garbage collected.
///
/// Purpose of the Finalizer:
/// Dispose(false) means disposing without needing to clean up managed resources. This is because
/// 1- Managed Resources: These are objects that the .NET garbage collector itself manages, like instances of other
/// .NET classes. They don't need to be explicitly disposed of by the finalizer
///
/// 2- Unmanaged Resources: These are resources that .NET does not manage, like file handles, database connections,
/// etc. The finalizer helps ensure that these are cleaned up if the Dispose method hasn't already been called.
/// </summary>
/// <remarks>
/// This class provides:
/// - Initialization and disposal of in-memory SQLite database.
/// - Creation of an instance of <see cref="ApplicationDbContext"/> for interaction with the database.
/// - Proper resource cleanup through implementation of <see cref="IDisposable"/> and <see cref="IAsyncDisposable"/>.
/// </remarks>

~BaseTest()
{
    Dispose(false);

When the Finalizer Runs:
     The finalizer is called by the garbage collector when it determines that the object is no longer reachable.
     Before reclaiming the memory used by the object, the garbage collector will execute the finalizer (if it exists).
 Why Dispose(false):
     When called by the finalizer, the Dispose method should not try to dispose of managed objects because
     they may have already been collected by the garbage collector.
     Therefore, you only pass false so the Dispose method only cleans up unmanaged resources.

Here is how to use it. 

 

public class BookRepositoryTests  : BaseTest
{
    private readonly IUnitOfWork _unitOfWork;
    
    public BookRepositoryTests()
    {
        _unitOfWork = new UnitOfWork(DbContextTest) ?? throw new ArgumentNullException(nameof(UnitOfWork));
    }
    
    [Fact]
    public void Ensure_Default_Data_Loaded()
    {
        // Arrange
        var defaultData = _unitOfWork.BookRepository.GetAll().ToList();
        
        // Act
        var countDefaultData = defaultData.Count;
        
        // Assert
        Assert.NotNull(defaultData);
        Assert.NotEqual(0, countDefaultData);
    }

[Fact]
public async Task TransactionalOperations_Books_Throws_Exception()
{
    // Arrange
    MockBookRepository.Setup(br => br.TransactionalOperationsAsync(
        It.IsAny<List<Book>?>(), It.IsAny<Func<DbSet<Book>,List<Book>,Task>?>(), 
        It.IsAny<List<Book>?>(), It.IsAny<Action<DbSet<Book>,List<Book>>>(), 
        It.IsAny<List<Book>?>(), It.IsAny<Action<DbSet<Book>,List<Book>>>()))
        .Throws(new NullReferenceException());
    
    // Act & Assert
    await Assert.ThrowsAsync<NullReferenceException>(
        async () => await MockUnitOfWork.Object.BookRepository.TransactionalOperationsAsync(
            null, null,
            null, null,
            null, null));
}}

 

So simple!

 


No files yet, migration hasn't completed yet!