5

Looking for some suggestion.

public class SomeController : Controller
{
    private readonly SomeContextClass _context;
     
    public SomeController(SomeContextClass context)
    {
        _context = context;
    }   
}

//Context Class
public SomeContextClass : DbContext
{
    public SomeContextClass(DbContextOptions<SomeContextClass> op) : base(op){

        Database.SetCommandTimeout(9000);
    }
}

//Test
[Fact]
public void SomeController_Test()
{
    //Trying In-Memory Implementation
    var options = new DbContextOptions<SomeContextClass>().UseInMemoryDatabase(databaseName: "SomeDB").Options;

    using (var context = new SomeContextClass(options))
    {
        var sc = new SomeController(context);
    }
}

This errors out when trying to initialize somecontext class due to setcommandtimeoutcall.

Is there a way to mock that call using MOQ? How to deal with it in in-memory test?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
fireholster
  • 622
  • 1
  • 9
  • 22

3 Answers3

3

Not really a solution, but I wanted to leave a pointer here for a workaround because this answer saved my day. I needed only one thing from the DatabaseFacade for my code to be unit-testable, and I ended up wrapping it with a virtual method of my database context that I CAN mock:

   public virtual bool CanConnectToDatabase()
   {
       return Database.CanConnect();
   }
cdonner
  • 37,019
  • 22
  • 105
  • 153
1

I had a look at this tonight, as I expected it's throwing an InvalidOperationException

Relational-specific methods can only be used when the context is using a relational database provider.

These are common with the in-memory provider when you try to use relational methods like FromSql, ExecuteSqlCommand etc. Most of the time they can be mocked using a mock wrapper (I do exactly this for my library EntityFrameworkCore.Testing), it is often a lot of work though.

SetCommandTimeout is an extension. You'll need to dig into the extension and mock a few things on the database facade to get it to work.

However before we go down that path, mocking isn't going to work for you in this instance. When you mock an implementation the mocking library needs to be able to create an instance to proxy over, which means it executes the code in the constructor. SetCommandTimeout will execute on whatever the DbContextOptions you pass in generates during mock creation and once again barf.

On to your options:

  1. Don't call set command timeout in your constructor; move it to another method, remember to invoke it etc

  2. Use a different entity framework core provider that supports relational operations

Can't say I like the first option, but it would allow you to mock it. The second is far more palatable. I don't do a lot with SQLite but a quick play in LINQPad is yielding expected results:

void Main()
{
    using (var connection = new SqliteConnection("Filename=:memory:"))
    {
        connection.Open();
        var options = new DbContextOptionsBuilder<TestDbContext>().UseSqlite(connection).Options;
        var testDbContext = new TestDbContext(options);
        Console.WriteLine(testDbContext.Database.GetCommandTimeout());
    }
}

public class TestDbContext : DbContext
{
    public TestDbContext(DbContextOptions<TestDbContext> op) : base(op)
    {
        Database.SetCommandTimeout(9000);
    }
}

Result:

enter image description here

rgvlee
  • 2,773
  • 1
  • 13
  • 20
1

Instead of hard-coding the timeout in the DbContext configuration you can set it through DbContextOptions. This way, the context class remains unchanged.

From the linked doc's example, you can set a provider-specific command timeout with :

optionsBuilder
    .UseSqlServer(connectionString, 
        providerOptions=>providerOptions.CommandTimeout(90));

This can be used in production or an integration test to set the command timeout for a real database, without modifying the DbContext:

var options = new DbContextOptions<SomeContextClass>()
        .UseSqlServer(connectionString, 
            providerOptions=>providerOptions.CommandTimeout(90))
        .Options;

The Timeout value can come from configuration, allowing it to change without modifying the code.

A unit test that uses the in-memory provider doesn't need that setting though, so it can simply be omitted.

var options = new DbContextOptions<SomeContextClass>()
                 .UseInMemoryDatabase(databaseName: "SomeDB")
                 .Options;
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • OP is using in memory context which doesn't support setting command timeout via options. – rgvlee Sep 24 '20 at 12:48
  • That's the point - the problem arises because the DbContext tries to set the setting whether the provider supports it or not. If the setting is set by the *caller* though, the DbContext won't have to guess. – Panagiotis Kanavos Sep 24 '20 at 12:53