106

I'm trying to create a unit test for a class that calls into an async repository. I'm using ASP.NET Core and Entity Framework Core. My generic repository looks like this.

public class EntityRepository<TEntity> : IEntityRepository<TEntity> where TEntity : class
{
    private readonly SaasDispatcherDbContext _dbContext;
    private readonly DbSet<TEntity> _dbSet;

    public EntityRepository(SaasDispatcherDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<TEntity>();
    }

    public virtual IQueryable<TEntity> GetAll()
    {
        return _dbSet;
    }

    public virtual async Task<TEntity> FindByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public virtual IQueryable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where(predicate);
    }

    public virtual void Add(TEntity entity)
    {
        _dbSet.Add(entity);
    }
    public virtual void Delete(TEntity entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual void Update(TEntity entity)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
    }

    public virtual async Task SaveChangesAsync()
    {
        await _dbContext.SaveChangesAsync();
    }
}

Then I have a service class that calls FindBy and FirstOrDefaultAsync on an instance of the repository:

    public async Task<Uri> GetCompanyProductURLAsync(Guid externalCompanyID, string productCode, Guid loginToken)
    {            
        CompanyProductUrl companyProductUrl = await _Repository.FindBy(u => u.Company.ExternalCompanyID == externalCompanyID && u.Product.Code == productCode.Trim()).FirstOrDefaultAsync();

        if (companyProductUrl == null)
        {
            return null;
        }

        var builder = new UriBuilder(companyProductUrl.Url);
        builder.Query = $"-s{loginToken.ToString()}";

        return builder.Uri;
    }

I'm trying to mock the repository call in my test below:

    [Fact]
    public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct()
    {
        var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable();

        var mockRepository = new Mock<IEntityRepository<CompanyProductUrl>>();
        mockRepository.Setup(r => r.FindBy(It.IsAny<Expression<Func<CompanyProductUrl, bool>>>())).Returns(companyProducts);

        var service = new CompanyProductService(mockRepository.Object);

        var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid());

        Assert.Null(result);
    }

However, when the test executes the call to the repository, I get the following error:

The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.

How can I properly mock the repository to get this to work?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Jed Veatch
  • 2,326
  • 3
  • 12
  • 13

10 Answers10

118

Thanks to @Nkosi for pointing me to a link with an example of doing the same thing in EF 6: https://msdn.microsoft.com/en-us/library/dn314429.aspx. This didn't work exactly as-is with EF Core, but I was able to start with it and make modifications to get it working. Below are the test classes that I created to "mock" IAsyncQueryProvider:

internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

    internal TestAsyncQueryProvider(IQueryProvider inner)
    {
        _inner = inner;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new TestAsyncEnumerable<TEntity>(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestAsyncEnumerable<TElement>(expression);
    }

    public object Execute(Expression expression)
    {
        return _inner.Execute(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return _inner.Execute<TResult>(expression);
    }

    public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
    {
        return new TestAsyncEnumerable<TResult>(expression);
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }
}

internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
    public TestAsyncEnumerable(IEnumerable<T> enumerable)
        : base(enumerable)
    { }

    public TestAsyncEnumerable(Expression expression)
        : base(expression)
    { }

    public IAsyncEnumerator<T> GetEnumerator()
    {
        return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
    }

    IQueryProvider IQueryable.Provider
    {
        get { return new TestAsyncQueryProvider<T>(this); }
    }
}

internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public TestAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

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

    public T Current
    {
        get
        {
            return _inner.Current;
        }
    }

    public Task<bool> MoveNext(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }
}

And here is my updated test case that uses these classes:

[Fact]
public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct()
{
    var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable();

    var mockSet = new Mock<DbSet<CompanyProductUrl>>();

    mockSet.As<IAsyncEnumerable<CompanyProductUrl>>()
        .Setup(m => m.GetEnumerator())
        .Returns(new TestAsyncEnumerator<CompanyProductUrl>(companyProducts.GetEnumerator()));

    mockSet.As<IQueryable<CompanyProductUrl>>()
        .Setup(m => m.Provider)
        .Returns(new TestAsyncQueryProvider<CompanyProductUrl>(companyProducts.Provider));

    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.Expression).Returns(companyProducts.Expression);
    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.ElementType).Returns(companyProducts.ElementType);
    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.GetEnumerator()).Returns(() => companyProducts.GetEnumerator());

    var contextOptions = new DbContextOptions<SaasDispatcherDbContext>();
    var mockContext = new Mock<SaasDispatcherDbContext>(contextOptions);
    mockContext.Setup(c => c.Set<CompanyProductUrl>()).Returns(mockSet.Object);

    var entityRepository = new EntityRepository<CompanyProductUrl>(mockContext.Object);

    var service = new CompanyProductService(entityRepository);

    var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid());

    Assert.Null(result);
}
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Jed Veatch
  • 2,326
  • 3
  • 12
  • 13
  • Glad you eventually figured it out. I was digging in the source code on github to see if they had any moq examples. Funny enough I was investigating one when I check and saw that you came to your own solution. Cool. – Nkosi Nov 08 '16 at 16:24
  • I would convert that to an extension method so you can reuse it in your tests. – Nkosi Nov 08 '16 at 16:25
  • 2
    Check answer given [here](http://stackoverflow.com/a/40500030/5233410) which references this answer where the extension method was used. Happy coding!!! – Nkosi Nov 09 '16 at 14:12
  • Thanks again, @Nkosi! – Jed Veatch Nov 09 '16 at 15:33
  • Works great, thanks! Perhaps you can add the following extension to your answer too? `public static class IEnumerableExtensions { public static IQueryable TestAsync(this IEnumerable source) { return new TestAsyncEnumerable(source); } }` – Peter Morris Oct 27 '17 at 15:37
  • This helped out a bunch after trying to implement EF Mocking in core and not realising they'd changed the IDbAsyncQueryProvider etc. Thanks. – JasperMoneyshot Dec 01 '17 at 15:32
  • How does `TestAsyncEnumerable` get the inner (source) enumeration when just it is returned from `ExecuteAsync` with expression as its constructor argument? I'm sure it get empty enumeration, doesn't? – Ilya Loskutov Mar 06 '18 at 13:44
  • Can you give same example for Insert and Update record? – Rashmin Javiya May 05 '18 at 20:35
  • 11
    Would you be willing to update this to core 3.0? I can't get passed "Argument expression is not valid" on _inner.Execute(expression) – BillRob Mar 16 '20 at 22:38
  • @BillRob Were you ever able to solve the updated to Core 3.0? I'm updating something right now to 3.1 and running into the same issue. – Napoleon Ike Jones Jul 21 '20 at 18:40
  • @NapoleonIkeJones I was not able to get around it. I ended up using InMemory EF database and randomly generated a new seed for each test execution. – BillRob Jul 22 '20 at 23:55
  • @BillRob and anybody else, I've found a work around for BillRob issue. https://stackoverflow.com/questions/63888111/how-to-remove-my-variable-to-tresult-casting-method – Mandelbrotter Sep 14 '20 at 16:15
  • 1
    @Mandelbrotter do you have an updated link? The one you used 404s. – Spencer Stream Sep 23 '20 at 18:03
  • 1
    @Spencer Stream No I do not. Seems I can't write a good post so I deleted it. I'll post the solution in this comment just give me a few moments to get it. – Mandelbrotter Sep 23 '20 at 18:11
  • 2
    public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken){ object returnValue = Execute(expression); return ConvertToThreadingTResult(returnValue); } – Mandelbrotter Sep 23 '20 at 18:16
  • 2
    TResult IAsyncQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken) { var returnValue = ExecuteAsync(expression, default); return ConvertToTResult(returnValue); } – Mandelbrotter Sep 23 '20 at 18:16
  • 2
    private static TR ConvertToTResult (dynamic toConvert) { return (TR)toConvert; } – Mandelbrotter Sep 23 '20 at 18:16
  • 2
    private static TR ConvertToThreadingTResult(dynamic toConvert) { return (TR)Task.FromResult(toConvert); } – Mandelbrotter Sep 23 '20 at 18:17
  • 2
    IAsyncEnumerable does not contain GetEnumerator, only GetAsyncEnumerator in .NET 5.0. None of these solutions work for .NET 5.0 – code_disciple Jul 14 '21 at 18:15
  • @Mandelbrotter Your solution helped me a great deal, but I almost didn't see it in the comments. I'm goin go to attempt to post an answer, but most of it was based on your follow up help, so thank you! – Matthew M. Jul 15 '21 at 18:39
  • For .NET Core 3.1 @Mandelbrotter 's fix can be replaced by this: https://stackoverflow.com/a/58314109/98422 – Gary McGill Jul 16 '21 at 08:46
78

Try to use my Moq/NSubstitute/FakeItEasy extension MockQueryable: supported all Sync/Async operations (see more examples here)

//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);

Notes:

  • AutoMapper is also supported from 1.0.4 ver
  • DbQuery supported from 1.1.0 ver
  • EF Core 3.0 supported from 3.0.0 ver
  • .Net 5 supported from 5.0.0 ver
R.Titov
  • 3,115
  • 31
  • 35
  • 10
    This seems to be a really nice package to use instead of writing a bunch of double implementation for a fake `IAsyncQueryProvider` etc. – infl3x Jun 26 '18 at 01:32
  • 1
    This is a great package, which did the trick right away. Thanks! – Sigge Jul 30 '19 at 18:34
  • 1
    what I don't get is where the _userRepository comes from in the first block??? or where the TestDbSetRepository comes from?? – Noob Sep 24 '19 at 12:22
  • 1
    @Noob try to open link from my comment, or just take a look https://github.com/romantitov/MockQueryable/blob/master/src/MockQueryable/MockQueryable.Sample/MyServiceMoqTests.cs – R.Titov Sep 30 '19 at 19:36
  • @R.Titov got any examples where you use firstordefaultasync, I'm kinda stuck, I asked for some help here > https://stackoverflow.com/questions/59500303/why-is-test-failing-when-using-firstordefaultasync-in-method , but didn't get any answers, am I using the library wrong? am I missing something? – Noob Dec 30 '19 at 16:02
  • 1
    @Noob in MyService.CreateUserIfNotExist I use FirstOrDefaultAsync, and here you can see tests for CreateUserIfNotExist logic https://github.com/romantitov/MockQueryable/blob/master/src/MockQueryable/MockQueryable.Sample/MyServiceMoqTests.cs – R.Titov Jan 02 '20 at 08:42
  • 1
    @R.Titov thanks, I found my mistake, first I wasn't mocking my data I was supposed to return and secondly, creating the mock inside the return(), gave also some other error. Anyway, got it working now, thanks!! – Noob Jan 02 '20 at 10:22
  • Since .NET 5 I'm getting "Could not load file or assembly 'Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'." when using MockQueryable.Moq. – Valuator Nov 11 '20 at 21:39
  • 1
    @Valuator .net 5 supported https://www.nuget.org/packages/MockQueryable.Moq/5.0.0 – R.Titov Nov 12 '20 at 14:14
10

Much less code solution. Use the in-memory db context which should take care of bootstrapping all the sets for you. You no longer need to mock out the DbSet on your context but if you want to return data from a service for example, you can simply return the actual set data of the in-memory context.

DbContextOptions< SaasDispatcherDbContext > options = new DbContextOptionsBuilder< SaasDispatcherDbContext >()
  .UseInMemoryDatabase(Guid.NewGuid().ToString())
  .Options;

  _db = new SaasDispatcherDbContext(optionsBuilder: options);
Dean Martin
  • 1,223
  • 3
  • 18
  • 27
  • This seems like a good solution. Why not? Add a reference to the in-memory nuget package to get the UseInMemoryDatabase extension method. https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory – andrew Jun 05 '18 at 11:31
  • Would this not be a form of integration test? I know it is an in-memory database, but you will be testing against a system which is what integration tests are. Correct me if I am wrong. – elfico Mar 26 '20 at 02:45
  • Integration tests should test systems which are integrated with each other. Since you are not leaving the context of the original system, or rather "mock" the external system by replacing it with the in-memory-mock system, you are not really leaving the unittest-context. – Chris Nov 15 '21 at 14:59
3

I'm maintaining two open-source projects that do the heavy lifting of setting up the mocks and actually emulates SaveChanges(Async).

For EF Core: https://github.com/huysentruitw/entity-framework-core-mock

For EF6: https://github.com/huysentruitw/entity-framework-mock

Both projects have Nuget packages with integration for Moq or NSubstitute.

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
3

I had some problems with the approved solution. Apparently, there were changes starting with Entity Framework 5.0.3. The IAsyncQueryProvider, IAsyncEnumerable and IAsyncEnumerator have different methods one must implement. I found an article online that provides a solution. This works for my .NET 6 application. Be sure to include the using Microsoft.EntityFrameworkCore.Query statement. For me, Visual Studio was having trouble finding the three interfaces and wanted me to create them manually.

using Microsoft.EntityFrameworkCore.Query;
using System.Linq.Expressions;
namespace MyApp.Tests
{
    internal class AsyncHelper
    {
        internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
        {
            private readonly IQueryProvider _innerQueryProvider;

            internal TestAsyncQueryProvider(IQueryProvider inner)
            {
                _innerQueryProvider = inner;
            }

            public IQueryable CreateQuery(Expression expression)
            {
                return new TestAsyncEnumerable<TEntity>(expression);
            }

            public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
            {
                return new TestAsyncEnumerable<TElement>(expression);
            }

            public object Execute(Expression expression) => _innerQueryProvider.Execute(expression);

            public TResult Execute<TResult>(Expression expression) => _innerQueryProvider.Execute<TResult>(expression);

            TResult IAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
            {
                Type expectedResultType = typeof(TResult).GetGenericArguments()[0];
                object? executionResult = ((IQueryProvider)this).Execute(expression);

                return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult))
                    .MakeGenericMethod(expectedResultType)
                    .Invoke(null, new[] { executionResult });
            }
        }

        internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
        {
            public TestAsyncEnumerable(IEnumerable<T> enumerable)
                : base(enumerable)
            { }

            public TestAsyncEnumerable(Expression expression)
                : base(expression)
            { }

            IQueryProvider IQueryable.Provider => new TestAsyncQueryProvider<T>(this);

            public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
                => new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
        }

        internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
        {
            private readonly IEnumerator<T> _enumerator;

            public TestAsyncEnumerator(IEnumerator<T> inner)
            {
                _enumerator = inner;
            }

            public T Current => _enumerator.Current;

            public ValueTask DisposeAsync() => new(Task.Run(() => _enumerator.Dispose()));

            public ValueTask<bool> MoveNextAsync() => new(_enumerator.MoveNext());
        }
    }
}

After creating this AsyncHelper, I was able to mock my database context.

IQueryable<MyEntity> myList = new List<MyEntity>
        {
            new()
            {
                Id= 6,
                FirstName = "John",
                MidName = "Q",
                LastName = "Doe",
            }
        }.AsQueryable();


        Mock<DbSet<MyEntity>> dbSetMock = new();

        dbSetMock.As<IAsyncEnumerable<MyEntity>>()
            .Setup(m => m.GetAsyncEnumerator(default))
            .Returns(new AsyncHelper.TestAsyncEnumerator<MyEntity>(myList.GetEnumerator()));

        dbSetMock.As<IQueryable<MyEntity>>()
            .Setup(m => m.Provider)
            .Returns(new AsyncHelper.TestAsyncQueryProvider<MyEntity>(myList.Provider));

        dbSetMock.As<IQueryable<MyEntity>>().Setup(m => m.Expression)
            .Returns(myList.Expression);

        dbSetMock.As<IQueryable<MyEntity>>().Setup(m => m.ElementType)
            .Returns(myList.ElementType);

        dbSetMock.As<IQueryable<MyEntity>>().Setup(m => m.GetEnumerator())
            .Returns(() => myList.GetEnumerator());

Mock<MyDbContext> mockContext = new();
mockContext.Setup(c => c.People).Returns(dbSetMock().Object);

Then, I arrange my unit test with my mock context.

        MyRepository myRepository = new(mockContext.Object);
        Person? person = await myRepository.GetPersonById(6);

Now, I can Assert any conditions without issue.

Assert.NotNull(person);
Assert.True(person.Id == 6);
Assert.True(person.FirstName == "John");

Hope this helps.

2

Here is a port of the accepted answer to F#, I just did it for myself and thought it may save someone the time. I have also updated the example to match the updated C#8 IAsyncEnumarable API and tweaked the Mock setup to be generic.

    type TestAsyncEnumerator<'T> (inner : IEnumerator<'T> ) =     

        let inner : IEnumerator<'T> = inner

        interface IAsyncEnumerator<'T> with
            member this.Current with get() = inner.Current
            member this.MoveNextAsync () = ValueTask<bool>(Task.FromResult(inner.MoveNext()))
            member this.DisposeAsync () = ValueTask(Task.FromResult(inner.Dispose))

    type TestAsyncEnumerable<'T> =       
        inherit EnumerableQuery<'T>

        new (enumerable : IEnumerable<'T>) = 
            { inherit EnumerableQuery<'T> (enumerable) }
        new (expression : Expression) = 
            { inherit EnumerableQuery<'T> (expression) }

        interface IAsyncEnumerable<'T> with
            member this.GetAsyncEnumerator cancellationToken : IAsyncEnumerator<'T> =
                 new TestAsyncEnumerator<'T>(this.AsEnumerable().GetEnumerator())
                 :> IAsyncEnumerator<'T>

        interface IQueryable<'T> with
            member this.Provider with get() = new TestAsyncQueryProvider<'T>(this) :> IQueryProvider

    and 
        TestAsyncQueryProvider<'TEntity> 
        (inner : IQueryProvider) =       

        let inner : IQueryProvider = inner

        interface IAsyncQueryProvider with

            member this.Execute (expression : Expression) =
                inner.Execute expression

            member this.Execute<'TResult> (expression : Expression) =
                inner.Execute<'TResult> expression

            member this.ExecuteAsync<'TResult> ((expression : Expression), cancellationToken) =
                inner.Execute<'TResult> expression

            member this.CreateQuery (expression : Expression) =
                new TestAsyncEnumerable<'TEntity>(expression) :> IQueryable

            member this.CreateQuery<'TElement> (expression : Expression) =
                new TestAsyncEnumerable<'TElement>(expression) :> IQueryable<'TElement>


    let getQueryableMockDbSet<'T when 'T : not struct>
        (sourceList : 'T seq) : Mock<DbSet<'T>> =

        let queryable = sourceList.AsQueryable();

        let dbSet = new Mock<DbSet<'T>>()

        dbSet.As<IAsyncEnumerable<'T>>()
            .Setup(fun m -> m.GetAsyncEnumerator())
            .Returns(TestAsyncEnumerator<'T>(queryable.GetEnumerator())) |> ignore

        dbSet.As<IQueryable<'T>>()
            .SetupGet(fun m -> m.Provider)
            .Returns(TestAsyncQueryProvider<'T>(queryable.Provider)) |> ignore

        dbSet.As<IQueryable<'T>>().Setup(fun m -> m.Expression).Returns(queryable.Expression) |> ignore
        dbSet.As<IQueryable<'T>>().Setup(fun m -> m.ElementType).Returns(queryable.ElementType) |> ignore
        dbSet.As<IQueryable<'T>>().Setup(fun m -> m.GetEnumerator ()).Returns(queryable.GetEnumerator ()) |> ignore
        dbSet
Ryan
  • 2,109
  • 1
  • 15
  • 19
  • You may want to ask a new question that refers to F# and answer with one. It does not belong to here. – ilkerkaran Jan 13 '20 at 13:33
  • 3
    The question does not mention the language. I also improved upon the accepted answer by adhering to the latest API. I don't think that deserves a markdown as it is useful. I am adding extra information for .Net programmers who land here. – Ryan Jan 13 '20 at 19:22
  • 2
    It contains C# tag – ilkerkaran Jan 14 '20 at 09:46
  • 1
    Either way man, I am not hurting anyone or clouding the topic, I am adding more useful information that is related directly to the question. I think you have been unfair but that is your perogative I guess. – Ryan Jan 15 '20 at 10:22
2

A way simpler approach is to write your own ToListAsync in one of the core layers. You dont need any concrete class implementation. Something like:

    public static async Task<List<T>> ToListAsync<T>(this IQueryable<T> queryable)
    {
        if (queryable is EnumerableQuery)
        {
            return queryable.ToList();
        }

        return await QueryableExtensions.ToListAsync(queryable);
    }

This also has the added benefit that you could use ToListAsync from anywhere in your app without needing to drag EF references all along.

nawfal
  • 70,104
  • 56
  • 326
  • 368
2

I know this question is old, but I found a nuget package to do this.

MockQueryable and MockQueryable.Moq

This does all of the work for you.

[TestCase("AnyFirstName", "AnyExistLastName", "01/20/2012", "Users with DateOfBirth more than limit")]
    [TestCase("ExistFirstName", "AnyExistLastName", "02/20/2012", "User with FirstName already exist")]
    [TestCase("AnyFirstName", "ExistLastName", "01/20/2012", "User already exist")]
    public void CreateUserIfNotExist(string firstName, string lastName, DateTime dateOfBirth, string expectedError)
    {
      //arrange
      var userRepository = new Mock<IUserRepository>();
      var service = new MyService(userRepository.Object);
      var users = new List<UserEntity>
      {
        new UserEntity {LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012", UsCultureInfo.DateTimeFormat)},
        new UserEntity {FirstName = "ExistFirstName"},
        new UserEntity {DateOfBirth = DateTime.Parse("01/20/2012", UsCultureInfo.DateTimeFormat)},
        new UserEntity {DateOfBirth = DateTime.Parse("01/20/2012", UsCultureInfo.DateTimeFormat)},
        new UserEntity {DateOfBirth = DateTime.Parse("01/20/2012", UsCultureInfo.DateTimeFormat)}
      };
      //expect
      var mock = users.BuildMock();
      userRepository.Setup(x => x.GetQueryable()).Returns(mock);
      //act
      var ex = Assert.ThrowsAsync<ApplicationException>(() =>
        service.CreateUserIfNotExist(firstName, lastName, dateOfBirth));
      //assert
      Assert.AreEqual(expectedError, ex.Message);
    }
3xGuy
  • 2,235
  • 2
  • 29
  • 51
1

Leveraging @Jed Veatch's accepted answer, as well as the comments provided by @Mandelbrotter, the following solution works for .NET Core 3.1 and .NET 5. This will resolve the "Argument expression is not valid" exception that arises from working with the above code in later .NET versions.

TL;DR - Complete EnumerableExtensions.cs code is here.

Usage:

public static DbSet<T> GetQueryableAsyncMockDbSet<T>(List<T> sourceList) where T : class
{
    var mockAsyncDbSet = sourceList.ToAsyncDbSetMock<T>();
    var queryable = sourceList.AsQueryable();
    mockAsyncDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
    mockAsyncDbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
    return mockAsyncDbSet.Object;
}

Then, using Moq and Autofixture, you can do:

var myMockData = Fixture.CreateMany<MyMockEntity>();
MyDatabaseContext.SetupGet(x => x.MyDBSet).Returns(GetQueryableAsyncMockDbSet(myMockData));
Matthew M.
  • 932
  • 10
  • 17
  • Here's EnumerableExtensions.cs with a fix for when an FirstOrDefaultAsync or similar method returns NULL: https://adm.ski/enumerableextensions – abieganski Mar 20 '23 at 16:40
1

For everyone who stuck at mocking DbContext with async queries, IAsyncQueryProvider and other things. Heres example usage of copy-paste types for netcore3.1 and higher. Based on generic DbContextCreation and generic DbSet seed.

    public class MyDbContext : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }
    }

    public class MyEntity
    {
        public Guid Id { get; set; }
    }

    internal class MockDbContextAsynced<TDbContext>
    {
        private readonly TDbContext _mock;
        public TDbContext Object => _mock;

        public MockDbContextAsynced()
        {
            _mock = Activator.CreateInstance<TDbContext>();
        }
          // suppressed. see full code in source below
    }

    [Fact]
    public void Test()
    {
        var testData = new List<MyEntity>
        {
            new MyEntity() { Id = Guid.NewGuid() },
            new MyEntity() { Id = Guid.NewGuid() },
            new MyEntity() { Id = Guid.NewGuid() },
        };

        var mockDbContext = new MockDbContextAsynced<MyDbContext>();
        mockDbContext.AddDbSetData<MyEntity>(testData.AsQueryable());

        mockDbContext.MyEntities.ToArrayAsync();
        // or
        mockDbContext.MyEntities.SingleAsync();
        // or etc.
        
        // To inject MyDbContext as type parameter with mocked data
        var mockService = new SomeService(mockDbContext.Object);
    }

For full implemented types see this source: https://gist.github.com/Zefirrat/a04658c827ba3ebffe03fda48d53ea11

Leonid Pavlov
  • 671
  • 8
  • 13