hawthorne
hawthorne

Reputation: 53

How to test using InMemoryDatabase if DbSet is set as required

My DbSet is required. I'd like to create test using InMemoryDatabase. However when I instantiate context I'm getting an error

Error (active) CS9035
Required member 'TestContext.Users' must be set in the object initializer or attribute constructor.

This is my code:

public class TestContext : DbContext
{
    public virtual required DbSet<User> Users { get; set; }

    public TestContext(DbContextOptions<TestContext> options) : base(options)
    {
    }
}

public class UserTests
{
    public UserTests()
    {
        var options = new DbContextOptionsBuilder<TestContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        TestContext testContext = new TestContext(options);
    }
}

I tried to set Users property using initializer like this:

new TestContext(options) { Users = null }

but of course it sets DbSet to null which is not what I want to achieve. I also inherited from the abstract class DbSet<User>, but then I'd need to implement this class and I want to avoid that. I also tried to inherit context but inherited properties also need to be required.

Is it a way to instantiate context without explicitly setting DbSet (please note that I'd like to keep required for DbSet, I also don't want to use any mocking frameworks)?

Upvotes: 0

Views: 58

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30340

I think you should be able to do this:

public virtual required DbSet<User> Users { get; set; } = default!;

For reference here's an example InMemory test database setup I use.

The DbContext using a required DbSet.

public sealed class InMemoryTestDbContext
    : DbContext
{
    public required DbSet<DboWeatherForecast> WeatherForecasts { get; set; } = default!;

    public InMemoryTestDbContext(DbContextOptions<InMemoryTestDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<DboWeatherForecast>().ToTable("WeatherForecasts");
    }
}

The services configuration and Test data loader.

services.AddDbContextFactory<InMemoryTestDbContext>(options
       => options.UseInMemoryDatabase($"TestDatabase-{Guid.NewGuid().ToString()}"));

var app = builder.Build();

// get the DbContext factory and add the test data
var factory = app.Services.GetService<IDbContextFactory<InMemoryTestDbContext>>();
if (factory is not null)
    TestDataProvider.Instance().LoadDbContext<InMemoryTestDbContext>(factory);

The Singleton test data provider.

public sealed class TestDataProvider
{
    public IEnumerable<DboWeatherForecast> WeatherForecasts => _weatherForecasts.AsEnumerable();

    private List<DboWeatherForecast> _weatherForecasts = new List<DboWeatherForecast>();

    public TestDataProvider()
    {
        this.Load();
    }

    private void Load()
    {

        var startDate = DateTime.Now;
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        _weatherForecasts = Enumerable.Range(1, 50).Select(index => new DboWeatherForecast
        {
            Date = startDate.AddDays(index),
            Temperature = new(Random.Shared.Next(-20, 55)),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToList();

    }

    public void LoadDbContext<TDbContext>(IDbContextFactory<TDbContext> factory) where TDbContext : DbContext
    {
        using var dbContext = factory.CreateDbContext();

        var dboWeatherForecasts = dbContext.Set<DboWeatherForecast>();

        // Check if we already have a full data set
        // If not clear down any existing data and start again

        if (dboWeatherForecasts.Count() == 0)
            dbContext.AddRange(_weatherForecasts);

        dbContext.SaveChanges();
    }

    private static TestDataProvider? _provider;

    public static TestDataProvider Instance()
    {
        if (_provider is null)
            _provider = new TestDataProvider();

        return _provider;
    }
}

Upvotes: 0

Related Questions