Why is my ef core data context disposed before use in a service under test?

1.1k Views Asked by At

Background: I am writing tests around services that make use of ef core. I want to use sqllite as it is relational.

I have written a base class for tests that will use a mock db factory I wrote to setup basic generic things like http mocking and the DAL.

namespace Bll.UnitTests
{
    public class TestBase : IDisposable
    {
        // pass httpclient as dependency, setup messageHandler for stubbing
        protected HttpClient httpClient;
        protected Mock<HttpMessageHandlerFake> fakeHttpMessageHandler = new Mock<HttpMessageHandlerFake> { CallBase = true };

        protected Mock<Logger> loggerMock;
        protected DalContext dataContext;

        protected MockDbFactory mockDbFactory;

        public TestBase()
        {
            mockDbFactory = new MockDbFactory();

            httpClient = new HttpClient(fakeHttpMessageHandler.Object);
            dataContext = mockDbFactory.testDb;
            loggerMock = new Mock<Logger>(dataContext);
        }

        public void Dispose()
        {
            mockDbFactory.Dispose();
        }
    }
}

Here is my mock db factory that should just setup a connection in memory and seems to work.

using Dal;
using Microsoft.EntityFrameworkCore;
using Moq;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;

namespace Bll.UnitTests.Factories
{
    // In-memory database only exists while the connection is open
    public class MockDbFactory : IDisposable
    {
        private SqliteConnection connection;
        public DalContext testDb;

        public MockDbFactory()
        {
            OpenConnection();
            testDb = GetTestDb();
        }

        public void Dispose()
        {
            CloseConnection();
        }

        private void OpenConnection()
        {
            connection = new SqliteConnection("DataSource=:memory:");
            connection.Open();
        }

        private void CloseConnection()
        {
            connection.Close();
        }

        private DalContext GetTestDb()
        {
            var options = new DbContextOptionsBuilder<DalContext>()
                .UseSqlite(connection)
                .Options;

            // Create the schema in the database
            using (var context = new DalContext(options))
            {
                context.Database.EnsureCreated();
                return context;
            }
        }
    }
}

In my test class datacontext is disposed when I debug my service under test.

public class LocationServiceTest : TestBase
    {
        private LocationService sut;

        public LocationServiceTest(): base()
        {
            sut = new LocationService(
                httpClient,
                loggerMock.Object,
                dataContext
            );
        }

        [Fact]
        public async Task UpdateCountriesAsync_CallsCountryApiAndUpdatesDatabase()
        {
            // arrange
            // setup get country data to return 2 countries
            var temp = BuildCountryApiReturnable(2);
            fakeHttpMessageHandler.Setup(f => f.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent(temp)
            });

            // act
            try
            {
                var result = await sut.UpdateCountriesAsync();

                // assert
                Assert.True(dataContext.Country.Count() == 2);
                Assert.True(dataContext.Location.Count() == 2);
            }
            catch(Exception e)
            {
                throw e;
            }

        }

I think I understand that a using statement is necessary as this will create my connection and dispose of it, but I am trying to do that manually so that I can inject the data context into my service. If I have to wrap everything in a using statement I will be forced to change my service..

1

There are 1 best solutions below

1
On BEST ANSWER

To answer your question: In your MockDbFactory, you already disposed the context by the using clause:

private DalContext GetTestDb()
{
    var options = new DbContextOptionsBuilder<DalContext>()
        .UseSqlite(connection)
        .Options;

    // Create the schema in the database
    using (var context = new DalContext(options))
    {
        context.Database.EnsureCreated();
        return context;  // context will already be disposed after returning
    }
}

You should initiate a new instance of DalContext and handle its disposal in your MockDbFactory.Dispose method instead:

private DalContext GetTestDb()
{
    ...
    testDb = new DalContext(options);
    //Other configurations
}
...
public void Dispose()
{
    CloseConnection();
    testDb.Dispose();
}