whiskytangofoxtrot
whiskytangofoxtrot

Reputation: 987

How can I create an Interface for my DbContext if I use EfCore.BulkExtensions

I am working on an ASP.Net Core 2.0 API that uses Entity Framework Core 2.0. I am trying to build up unit tests using XUnit and Moq but I am running into an issue with creating an interface for my DbContext so I can mock it in my unit tests.

Currently, my project is not using an interface for my context. I am injecting it into my repository classes as its implementation. And in my Startup.cs I am using services.AddDbContext to set it up.

Example of a typical repository class constructor.

    public CompaniesRepository(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

Example of Startup.cs

        services.AddDbContext<MyDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("MyDbConnectionString"),
            sqlOptions =>
            {
                sqlOptions.EnableRetryOnFailure(5,TimeSpan.FromSeconds(30),sqlTransientErrors);
            });
        });

And this method has been working like this just fine.

However, now I am trying to set up unit tests and I want to be able to Mock my context so I need to create an Interface for it.

So, I added an interface to MyDbContext called IMyDbContext and added the following code to my Startup.cs, following recommendations in this blog post by Jerrie Pelser

        services.AddScoped<IMyDbContext>(provider => provider.GetService<MyDbContext>());

Which seemed to work, except for one issue. I am also using Boris Djurdjevic's EFCore.BulkExtensions NuGet and so, I am getting a compile error coming from my repository classes, that now inject the IMyDbContext interface in their constructors, stating that my interface does not contain a definition for BulkInsert:

Error CS1929 'IMyDbContext' does not contain a definition for 'BulkInsert' and the best extension method overload 'DbContextBulkExtensions.BulkInsert(DbContext, IList, BulkConfig, Action)' requires a receiver of type 'DbContext'

I assume that I need to add the BulkInsert extension method to my IMyDyBontext interface somehow but I am not sure how to do that correctly. If I just try adding the method to my interface, then I get an error saying it is not implemented in my MyDbContext class, of course.

How do I reference the BulkInsert extension method in my MyDbContext class?

Upvotes: 2

Views: 3299

Answers (2)

MSansom
MSansom

Reputation: 21

I recently encountered this issue while preparing to mock my DbContext for unit testing when using the EF Core BulkExtensions package. The response marked as the correct answer is ALMOST correct (although they did say that they had not tested their code).

When implementing this inside the DbContext class in the way described above you will receive a StackOverflowException when calling the BulkInsert method.

So, I started debugging and decided to put a breakpoint on the method inside the DbContext class to see what was going on. This led me to understand the reason for the StackOverflowException: This method was calling itself instead of the method on the base DbContext class due to the scope of the "this" keyword.

The solution was fairly straightforward, solved by the following change to the BulkInsert method implementation inside the MyDbContext implementation:

public async Task BulkInsertAsync<T>(IList<T> entities, BulkConfig bulkConfig = null, Action<decimal> progress = null) where T : class
{
    await ((DbContext)this).BulkInsertAsync<T>(entities, bulkConfig, progress);
}

Also, add the following line to your IMyDbContext interface:

Task BulkInsertAsync<T>(IList<T> entities, BulkConfig bulkConfig = null, Action<decimal> progress = null) where T : class;

This ensured that the scope of "this" was cast to the base DbContext class type instead of the type of my own DbContext implementation. Therefore, instead of calling itself, it called the extension method using the base DbContext class.

Using this solution, you should now be able to successfully use the extension method in your code as shown below:

await _context.BulkInsertAsync<User>(users);

Hopefully someone finds this useful. It wasn't too bad to figure out through debugging and ensured I could keep all of my methods easily unit-testable when using the BulkExtensions package!

Upvotes: 2

Win
Win

Reputation: 62290

BulkInsert is an extension method. You cannot mock static method easily.

Instead, you could implement it inside MyDbContext class. Then, you could mock IMyDbContext for unit testing. Note: I did not test it.

public interface IMyDbContext
{
    void BulkInsert<T>(IList<T> entities, BulkConfig bulkConfig = null,
        Action<decimal> progress = null) where T : class;

}

public class MyDbContext : DbContext, IMyDbContext
{
    public void BulkInsert<T>(IList<T> entities, BulkConfig bulkConfig = null,
        Action<decimal> progress = null) where T : class
    {
        this.BulkInsertOrUpdate(entities, bulkConfig, progress);
    }
}

Upvotes: 2

Related Questions