88

I am using ASP.NET Core 2.2, EF Core and MOQ. When I run the test I am getting this error:

Message: System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member: x => x.Movies

What I am doing wrong?

public class MovieRepositoryTest
{
    private readonly MovieRepository _sut;

    public MovieRepositoryTest()
    {
        var moviesMock = CreateDbSetMock(GetFakeListOfMovies());
        var mockDbContext = new Mock<MovieDbContext>();
        mockDbContext.Setup(x => x.Movies).Returns(moviesMock.Object);
        _sut = new MovieRepository(mockDbContext.Object);
    }

    [Fact]
    public void GetAll_WhenCalled_ReturnsAllItems()
    {
        //Act
        var items = _sut.GetAll();

        //Assert
        Assert.Equal(3, items.Count());
    }

    private IEnumerable<Movie> GetFakeListOfMovies()
    {
        var movies = new List<Movie>
        {
            new Movie {Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action"},
            new Movie {Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action"},
            new Movie {Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action"}
        };

        return movies;
    }

    private static Mock<DbSet<T>> CreateDbSetMock<T>(IEnumerable<T> elements) where T : class
    {
        var elementsAsQueryable = elements.AsQueryable();
        var dbSetMock = new Mock<DbSet<T>>();

        dbSetMock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(elementsAsQueryable.Provider);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.Expression).Returns(elementsAsQueryable.Expression);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(elementsAsQueryable.ElementType);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(elementsAsQueryable.GetEnumerator());

        return dbSetMock;
    }
  }

And this is my DB Context, with the Movie dbSet:

public class MovieDbContext: DbContext
{
    public MovieDbContext(DbContextOptions<MovieDbContext> options) : base(options)
    {

    }

    public DbSet<Movie> Movies { get; set; }
}

And the Repository with the method GetAll to be tested:

 public class MovieRepository: IMovieRepository
{
    private readonly MovieDbContext _moviesDbContext;
    public MovieRepository(MovieDbContext moviesDbContext)
    {
        _moviesDbContext = moviesDbContext;
    }

    public IEnumerable<Movie> GetAll()
    {
        return _moviesDbContext.Movies;
    }
}
TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
MarcosF8
  • 1,848
  • 5
  • 19
  • 33
  • Can show the method that you are going to test? – TanvirArjel Jan 16 '19 at 14:58
  • 1
    EF Core 2 has an [in memory provider](https://learn.microsoft.com/en-us/ef/core/providers/in-memory/) which negates the need for mocking the context, it's much nicer to use. See [here](https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory). – DavidG Jan 16 '19 at 14:58
  • 3
    The `Movies` property in `MovieDbContext` has to be defined as `virtual` in order to be correctly mocked – haim770 Jan 16 '19 at 14:58
  • @DavidG Yes! That's why I have asked him to show his method to be tested! – TanvirArjel Jan 16 '19 at 14:59
  • Thanks, I updated the post showing the repository code as well – MarcosF8 Jan 16 '19 at 15:05
  • Seems redundant to use Repository pattern on top of dbcontext as dbcontext itself uses UoW & repository pattern. Also would save you the trouble of unit testing against said repository. – alans Aug 24 '20 at 17:42
  • Your answer helped me out on how to create the fake list for DbSet. So thanks! :) – Svenmarim Oct 28 '20 at 09:59

6 Answers6

181

I see you are using EF core DbContext in your MovieRepository. So instead of using mock, Using EF Core InMemory database will be a great option for you. This will also reduce the complexity.

Write your GetAllTest() method as follows:

[Fact]
public void GetAllTest()
{
        var options = new DbContextOptionsBuilder<MovieDbContext>()
            .UseInMemoryDatabase(databaseName: "MovieListDatabase")
            .Options;

        // Insert seed data into the database using one instance of the context
        using (var context = new MovieDbContext(options))
        {
            context.Movies.Add(new Movie {Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action"});
            context.Movies.Add(new Movie {Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action"});
            context.Movies.Add(new Movie {Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action"});
            context.SaveChanges();
        }

        // Use a clean instance of the context to run the test
        using (var context = new MovieDbContext(options))
        {
            MovieRepository movieRepository = new MovieRepository(context);
            List<Movies> movies == movieRepository.GetAll();

            Assert.Equal(3, movies.Count);
        }
}

Note: Don't forget to install Microsoft.EntityFrameworkCore.InMemory nuget package as follows:

Install-Package Microsoft.EntityFrameworkCore.InMemory

For more details: Testing with InMemory

Dinah
  • 52,922
  • 30
  • 133
  • 149
TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • 62
    That is not unit testing. That's integration testing. The whole point of unit testing is remove dependencies from unit test. So using the ImMemory database is just another dependency. – Alexander Pavlenko Apr 08 '20 at 12:38
  • @AlexanderPavlenko why do we test this part of EF at all? For example, if we are testing a service that has a GetAll method that interacts with dbcontext to use ef provider to interact with db, why not just create what we assume the database will return and use this? What benefit is there in testing EF "adding" the movies? Id rather know that my service is mapping the objects used throughout the app appropriately to database type object, no? Why not just test our service? – MZawg Apr 10 '20 at 13:46
  • 8
    @MZawg We test this part because our service depends on EF DbContext. Actually that unit test should mock or stub all dependencies. And make test service functionality without any dependency. The main quality of the unit test is speed. But If your service has EF dependency like some DbContext implementation then you going to ask yourself how are you going to mock or stub this dependency. The good choice is using the Repository pattern where you can wrap DbContext. But here we are talking about for the built-in ability of EF to handle tests. – Alexander Pavlenko Apr 11 '20 at 19:13
  • @AlexanderPavlenko makes a lot of sense when you put it like that. Ughhh that's no fun though lol – MZawg Apr 14 '20 at 15:51
  • 5
    The EF Core in-memory database is not suitable for unit testing as per https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/#approach-3-the-ef-core-in-memory-database – Bulgur Jul 08 '20 at 08:42
  • 10
    @Bulgur That's not necessarily true. You could use it, but: "Just don't do this to test actual database queries or updates." – 321X Jul 20 '20 at 06:24
  • 28
    @Bulgur The above-mentioned link says this about mocking DBContext: "However, we never try to mock DbContext or IQueryable. Doing so is difficult, cumbersome, and fragile. Don't do it." Instead: "use the EF in-memory database when unit testing something that uses DbContext." + What 321X pointed. – Rohin Tak Aug 25 '20 at 19:02
  • The in memory context fails for a number of reasons due to its implementation. One example is that it doesn't support transactions so if you use that in your code it will fail to create those and you have to implement functionality in your solution to circumvent failures by the In memory database just in order to test your code. It will also fail in other cases with database setup. It's not a good fit for unit testing. – GeirGrusom Apr 19 '21 at 09:35
  • +1 for specifying the actual package to install, and the namespace to "using". A thing that the geniuses out there forget 9 times out of 10. – jeancallisti Nov 25 '21 at 10:40
  • System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Database.Transaction.TransactionIgnoredWarning': Transactions are not supported by the in-memory store. – malat Mar 02 '23 at 13:39
  • It is true that Microsoft doesn't recommend using in memory database for testing purposes. Some may argue . But I think main problem with in memory testing its' limitation.It doesn't allow transactions and raw SQL. – erhan355 Mar 31 '23 at 01:32
30

To save your time, try to use my Moq/NSubstitute extension MockQueryable: https://github.com/romantitov/MockQueryable supported all Sync/Async operations

//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
 new UserEntity,
 ...
};

//2 - build mock by extension
var mock = users.AsQueryable().BuildMock();

//3 - setup the mock as Queryable for Moq
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);

//3 - setup the mock as Queryable for NSubstitute
_userRepository.GetQueryable().Returns(mock);

DbSet also supported

//2 - build mock by extension
var mock = users.AsQueryable().BuildMockDbSet();

//3 - setup DbSet for Moq
var userRepository = new TestDbSetRepository(mock.Object);

//3 - setup DbSet for NSubstitute
var userRepository = new TestDbSetRepository(mock);

Note:

  • AutoMapper supported from 1.0.4 ver
  • DbQuery supported from 1.1.0 ver
  • EF Core 3.0 supported from 3.0.0 ver
R.Titov
  • 3,115
  • 31
  • 35
  • I'm confused by this. How do you setup multiple tables on a single DbContext? – Ristogod Dec 20 '19 at 14:43
  • @Ristogod you can return different mock objects for different collections in DbContext. – R.Titov Jan 03 '20 at 13:51
  • Does this support relational aspects of the entities, like foreign keys, and navigation properties? – Tom Mar 27 '20 at 22:16
  • @Tom Depend on your test collection. I would recommend you to try it first. – R.Titov Mar 28 '20 at 11:57
  • 2
    @R.Titov so I believe if i am testing a generic repository code (which is an internal library) then I can just use this without being worried about the how DB behaves, becasue my goal as unit test is to test the Generic Repository code and not what DB returns to me ! Am I Correct? – kuldeep Apr 05 '20 at 05:43
  • 1
    @kuldeep yes. Tests with real database are more integration then unit tests – R.Titov Apr 06 '20 at 07:08
  • This is working great with both sync and async code. Thank you! – Alexei - check Codidact Oct 03 '20 at 09:04
  • I tried this out cause I love the simplicity, but it doesn't provide an implementation for `.Find()`, so that broke a bunch of my tests. – krillgar Aug 02 '22 at 12:54
  • 1
    @krillgar check the test DbSetFindAsyncUserEntity() from https://github.com/romantitov/MockQueryable/blob/master/src/MockQueryable/MockQueryable.Sample/MyServiceMoqTests.cs – R.Titov Aug 03 '22 at 08:53
  • @R.Titov Awesome, thank you! That looks exactly like how I set that up in my little POC I was doing for work, and is easy to implement. I really appreciate that. – krillgar Aug 03 '22 at 19:29
  • BuildMock gonna be available to use if you use MockQueryable.Moq package. – ddagsan May 26 '23 at 12:52
26

Use the Moq.EntityFrameworkCore package.

It is as easy as:

using Moq.EntityFrameworkCore;

var myDbContextMock = new Mock<MyDbContext>();
var entities = new List<Entity>() { new Entity(), new Entity() };
myDbContextMock.Setup(x => x.Entities).ReturnsDbSet(entities);
DLeh
  • 23,806
  • 16
  • 84
  • 128
Kolazomai
  • 806
  • 7
  • 6
  • 1
    If I only include `using Moq.EntityFrameworkCore;` at the top of my file, the compiler does not recognise the class `Mock<>`, I have to also include `using Moq;` and then your solution does not work, however in the [example test file of the repo](https://github.com/MichalJankowskii/Moq.EntityFrameworkCore/blob/master/src/Moq.EntityFrameworkCore.Examples/UsersServiceTest.cs), they dont seem to need to reference Moq, only Moq.EntityFrameworkCore? – bongoSLAP Jul 17 '22 at 15:54
  • You are right, they use `var userContextMock = new Mock();`, but they do not use `using Moq;`. However, the Moq package is referenced in their [project file](https://github.com/MichalJankowskii/Moq.EntityFrameworkCore/blob/master/src/Moq.EntityFrameworkCore.Examples/Moq.EntityFrameworkCore.Examples.csproj), so I assume it is included somehow. I did not try out their examples. In any case, I have already used the Moq.EntityFrameworkCore library successfully in my own projects using an adaption of the code I posted which follows the official "Usage" documentation. – Kolazomai Jul 19 '22 at 14:12
  • `Moq.EntityFrameworkCore` actually only supports .Net6.0, not core :( Would have been an awesome solution for me if it had worked on .net core :( – Dan Rayson Sep 13 '22 at 15:49
  • 3
    @DanRayson Just use a prior version of the package. – Delonous Nov 01 '22 at 19:26
  • 1
    I like this as a solution for those who've opted out of using the repository pattern with EF Core. Simple & elegant – jarodsmk Jan 30 '23 at 09:45
3

This is a development of R.Titovs answer done in ASP.NET Core 3.1:

Constructing the Moq (generic method)

The data is cloned to allow for tests to run in parallel and prevent a test to access data changed by another.

public static Mock<DbSet<TEnt>> SetDbSetData<TEnt>(this Mock<IApplicationDbContext> dbMock,
        IList<TEnt> list, bool clone = true) 
    where TEnt : class
{
    var clonedList = clone ? list.DeepClone().ToList() : list.ToList();
    var mockDbSet = clonedList.AsQueryable().BuildMockDbSet();

    dbMock.Setup(m => m.Set<TEnt>()).Returns(mockDbSet.Object);
    dbMock.Setup(m => m.ReadSet<TEnt>()).Returns(mockDbSet.Object.AsQueryable());

    return mockDbSet;
}

Using some test data

_appUserDbSetMock = _dbMock.SetDbSetData(ApplicationUserTestData.ApplicationUserData);

Example test

[Fact]
private async Task Handle_ShouldAddANewUser()
{
    var command = new CreateApplicationUserCommand
    {
        // ...
    };

    await _handler.Handle(command, default);

    _appUserDbSetMock.Verify(m => m.AddAsync(It.IsAny<ApplicationUser>(), default), Times.Once);
}

One advantage of using MoqQueryable is that there is no need for a generic repository since DbSet acts like one and the mocking is very easy.

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
0

The error you're receiving is because you need to declare the Movies property on your dbcontext as Virtual.

As someone pointed out in the comments, you should use EF's built in memory provider for testing.

Michael Brown
  • 9,041
  • 1
  • 28
  • 37
-1

setup your dependency injection for the unit test project (dot.net core 5 and xunit 2.4)

1. add a startup.cs file with a class Startup 
2.  public void ConfigureServices(IServiceCollection services)
    {
        var configuration = new ConfigurationBuilder()
      .SetBasePath(System.IO.Directory.GetCurrentDirectory())
      .AddJsonFile("appsettings.Development.json", false, true)
      .Build();
        //setups nlog dependency injection

        services.AddControllers();

        var connectionstring = configuration.GetConnectionString("DbCoreConnectionString");

        services.AddDbContext<ViewpointContext>(options1 => options1.UseSqlServer(connectionString));

        services.AddScoped<IRepositoryDB, RepositoryDB>();

        services.ConfigureLoggerService();

    }

 3. in your xunit class add your dependency injection
      IRepositoryDB _db;
      public TestSuite(ITestOutputHelper output,IRepositoryDB db)
    {
         _db=db;
    }
Golden Lion
  • 3,840
  • 2
  • 26
  • 35