6

I'm using the in memory database provider for integration tests however I don't seem to be able to update a record. I've run the same code against a real SQL database and everything gets updated fine. Here is my test fixture code.

Test Fixture:

public class TestFixture<TStartup> : IDisposable
{
    private readonly TestServer _testServer;
    public HttpClient TestClient { get; }
    public IDatabaseService DbContext { get { return _testServer.Host.Services.GetService<DatabaseService>(); } }

    public TestFixture() : this(Path.Combine("src")) { }

protected TestFixture(string relativeTargetProjectPatentDir)
{
    Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Testing");

    var builder = new WebHostBuilder()
        .ConfigureServices(services =>
        {
            services.AddDbContext<DatabaseService>(options =>
                options.UseInMemoryDatabase("TestDB")
                .EnableSensitiveDataLogging());
        })
        .UseEnvironment("Testing")
        .UseStartup<Startup>();

    _testServer = new TestServer(builder)
    {
        BaseAddress = new Uri("http://localhost:5010")
    };

    TestClient = _testServer.CreateClient();
    TestClient.BaseAddress = _testServer.BaseAddress;
}

    public void Dispose()
    {
        TestClient.Dispose();
        _testServer.Dispose();
    }
}

I've spent most of the day googling this and not come across any other people talking about it so I'm assuming its probably my issue rather than a EF bug. I'm sure someone would have noticed a DB that you can't update.

Chris Sainty
  • 7,861
  • 2
  • 37
  • 59
  • This related question can also help: https://stackoverflow.com/questions/54264787/unit-testing-with-ef-core-and-in-memory-database – Jess Dec 30 '21 at 15:28

5 Answers5

6

Updating works with Singleton but I have CQRS architecture and to check if the entry was updated in e2e test I have to reload entry

Context.Entry(entity).Reload();

I hope that this can help someone

ZeSzymi
  • 116
  • 1
  • 6
  • That did help me indeed but I'm curious why is that needed in some cases while in other cases it seems to work just fine? – Saher Ahwal Jun 30 '21 at 19:03
  • I would give you +2 if I could. +1 for helping me out and +1 for mentioning the CQRS pattern which I am using, but not quite correctly. – Jess Dec 30 '21 at 15:22
  • @SaherAhwal If you could share the cases then maybe I could respond – ZeSzymi Jun 30 '22 at 14:45
4

It turned out that changing the lifetime of my DbContext in my test fixture to singleton solved my issue.

Chris Sainty
  • 7,861
  • 2
  • 37
  • 59
  • I used the same strategy but I did it using an XUnit attribute (to make my tests run sequentially): https://stackoverflow.com/a/55700172/495455 – Jeremy Thompson Apr 16 '19 at 03:35
  • Thanks for the answer! This was really causing me a lot of confusion. I added to this post the change I made to get this working based on your answer. – JCisar Jan 10 '20 at 21:06
  • but DbContext is not thread safe - how did that solve the problem? – Saher Ahwal Jun 30 '21 at 16:51
1

Well it can be that DbContext is used in wrong way. I had the same problem. I used the DbContext in same way as you. I simply returned the instance by .Host.Services.GetService<TContext>. The problem with this approach is that DbContext will never release tracked entities so either you set entity State as EntityState.Detached and you force DbContext to reload it, or you will use scopes.

using (var scope = _testServer.Host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
  var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseService>();
  //make any operations on dbContext only in scope
}
Anton Kalcik
  • 2,107
  • 1
  • 25
  • 43
0

Adding to Chris's answer. Here is an example of what I had vs. what fixed the issue:

services.AddDbContext<TestDbContext>(options => {
 options.UseInMemoryDatabase("TestDb");
});

to

var options = new DbContextOptionsBuilder<TestDbContext>()
                        .UseInMemoryDatabase(databaseName: "TestDb")
                        .Options;
services.AddSingleton(x => new TestDbContext(options));
JCisar
  • 2,584
  • 2
  • 29
  • 28
0

Using AsNoTracking behavior may additionally work below,

services.AddDbContext<TestDbContext>(
            a => a.UseInMemoryDatabase(databaseName: "TestDb").UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking),
            ServiceLifetime.Singleton)

Also, how are you updating record? This seems to track in EFCore InMemory,

_dbContext.Entry(modifyItem).State = EntityState.Modified;
            

However, this doesn't seem to work as much.

_dbContext.Entry(existingItem).CurrentValues.SetValues(modifyItem);