zzzprojects / EntityFramework-Plus

Entity Framework Plus extends your DbContext with must-haves features: Include Filter, Auditing, Caching, Query Future, Batch Delete, Batch Update, and more
https://entityframework-plus.net/
MIT License
2.27k stars 318 forks source link

Running parallel test using InMemoryDatabase - how to facilitate the global requirement of EntityFrameworkManager #794

Closed stevie1706 closed 9 months ago

stevie1706 commented 9 months ago

1. Description

How can we make use of the library in the scenario where we want to run tests in parallel, given the test host is global, if we wanted to use a database per test class, how can we facilitate the requirement of EntityFrameworkManager.ContextFactory

It appears when running in parallel the global parameter cannot be scoped to the test class itself, changing the global instance of the DB will affect all other tests.

JonathanMagnan commented 9 months ago

Hello @stevie1706 ,

The EntityFrameworkManager.ContextFactory provides a context so you can give him a context factory back. We almost do not use it anymore in the more recent version of our library (if not at all in multiple cases).

If that option has been configured correctly, it should not be a problem even in parallel tasks, as you can provide multiple contexts depending on the current context or even options in the current context.

For example:

// Using a constructor that depends on the current context
EntityFrameworkManager.ContextFactory = context =>
{
    if (context is EntityContext entityContext)
    {
        if(entityContext.CustomPropertyThatProvideOptionInfo != null)
        {
            return new EntityContext(entityContext.CustomPropertyThatProvideOptionInfo);
        }
        else
        {
            return new EntityContext();
        }       
    }
    else
    {
        return new ElseContext();
    }
};

I can currently handle multiple different contexts, and it should not be a problem even in concurrency scenarios.

However, the ContextFactory should be set globally only once (not in every test, unlike you specifically always use the same code).

Let me know if that answers your question.

Best Regards,

Jon

stevie1706 commented 9 months ago

Thanks Jon that helped a lot - will try it out and report back. Appreciate the help.

stevie1706 commented 9 months ago

Hi @JonathanMagnan ,

I tried the following basic test fixture. In the hope that the context factory would be called multiple times based on number of tests being run. The idea was I would store the inmemory database test name on each instance of the context. This could be then used to generate a new context with the correct database name for the test.

public class GlobalTestSetupFixture
{
    private List<TestSetupConfiguration> _testSetupConfigurations { get; set; }
    public GlobalTestSetupFixture()
    {
        _testSetupConfigurations = new List<TestSetupConfiguration>();
        EntityFrameworkManager.ContextFactory = context =>
        {
            if (context is MyContext entityContext)
            {
                //I was expecting this ContextFactory to be invoked once for each test but it appeared to only be hit once when running multiple tests - therefore it is resolving the first context only for subsequent tests.  
                var dbName = entityContext.DatabaseNameForTestingOnly;
                var testSetupConfiguration = GetTestSetupConfiguration(dbName);

                return new MyContext (testSetupConfiguration.DbContextOptions, testSetupConfiguration.SettableUserResolverService);
            }
            return null;
        };
    }

    public void AddTestSetupConfiguration(TestSetupConfiguration testSetupConfiguration)
    {
        if(_testSetupConfigurations.Any(x => x.DatabaseName == testSetupConfiguration.DatabaseName))
        {
            return;
        }
        _testSetupConfigurations.Add(new TestSetupConfiguration(testSetupConfiguration.DatabaseName, testSetupConfiguration.SettableUserResolverService, testSetupConfiguration.DbContextOptions));
    }

    public TestSetupConfiguration GetTestSetupConfiguration(string databaseName)
    {
        return _testSetupConfigurations.First(x => x.DatabaseName == databaseName);
    }
}

It appears that this context factory is used for Single and BulkMerge statements. Is there any way to achieve the desired result here? Many thanks for your help in advance.

JonathanMagnan commented 9 months ago

Hello @stevie1706 ,

Do you think you could create a runnable project with the issue? It doesn’t need to be your project, just a new solution with the minimum code to reproduce the issue. You can send it in private here if needed: info@zzzprojects.com

That will be easier for us to understand and debug your case with a runnable project.

Best Regards,

Jon

stevie1706 commented 9 months ago

Thanks @JonathanMagnan - reproducing the issue helped me figure out what was wrong. I think I just needed a static instance of a class to hold the relevant db names.

Have included a small example project in case it is of use to others in the future.

Thanks again for your help!

[github.com/stevie1706/efplus-parallel-tests-inmemory-example/tree/master](github.com/stevie1706/efplus-parallel-tests-inmemory-example/tree/master

JonathanMagnan commented 9 months ago

Hello @stevie1706 ,

Awesome ;) thank a lot for sharing the project.

Best Regards,

Jon