3

I am trying to tell a method GetAll() on a mocked object _portalUserRepositoryMock to return an object of type IQueryable<TEntity>. I know it is of this type because the method in the class to be tested returns this type.

I've not been able to come up with a solution. I saw this post, but had errors trying to include the library into my project. Something about the version of Microsoft.EntityFrameworkCore - which led to more issues.

What I did to get this error was:

_portalUserRepositoryMock = Substitute.For<IPortalUserRepository>();
_portalUserRepositoryMock.GetAll().Returns(fakeQueryablePUser.AsQueryable());

The class under test uses the repository like this:

var portal = await _portalUserRepository.GetAll().Include(p => 
p.Portal).Where(p => p.UserId == user.Id && p.Portal.PortalType == 
dto.PortalType).FirstOrDefaultAsync();

and the GetAll() method is:

        public IQueryable<TEntity> GetAll()
    {
        try
        {
            return DbContext.Set<TEntity>().AsNoTracking();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

I get this error:

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

I reckon I'm getting this error because of the FirstOrDefaultAsync() that is being used. Just have no idea on how to resolve it.

Edit: I have now been able to add the MockQueryable library to my test project (by using version 1.0.4 and not the latest 1.1.0 ). I have followed the steps, as shown below:

        var fakePortalUser = new PortalUser()
        {
            PortalId = new Guid()
        };

        var fakeQueryablePUser = new List<PortalUser>
        {
            fakePortalUser
        }.AsQueryable().BuildMock();

Last step is now to use GetQueryable(). Which I try to use here:

_portalUserRepositoryMock.GetAll().GetQueryable().Returns(fakeQueryablePUser);

But I get the red squigly line under theGetQueryable() method call. So code won't compile.

user54287
  • 69
  • 9
  • Are you using the .NET Framework or .Net Core? – Gabriel Luci Sep 05 '19 at 15:01
  • I am using .Net Core. 2.1 to be specific. – user54287 Sep 05 '19 at 15:04
  • I didn't understand the question completely so I'll try to help via a comment. Did you try something like this `Enumerable.Empty().AsQueryable();`? – Amir Popovich Sep 05 '19 at 15:08
  • 1
    @user54287 This issue is as you suspected with `FirstOrDefaultAsync`. That extension expects the queryable to also be derived from `IDbAsyncQueryProvider` in order to match async EF which the mock wont be. Just as the exception stated. – Nkosi Sep 05 '19 at 15:11
  • @Amir Popovich; No I have not tried that. I am trying to get the mocked object to return `fakeQueryablePUser`. Whcih is a `List`. Then to convert this list to queryable, I used the etension method `AsQueryable`. Hope this sheds some more light on the issue. – user54287 Sep 05 '19 at 15:13
  • @Nkosi: What can I change to ensure what I return is of the expected type? I'm lost. Many thanks in advance. – user54287 Sep 05 '19 at 15:16
  • 1
    @user54287 - If the `List` is a hardcoded in memory list, then `AsQueryable` will work from the box. – Amir Popovich Sep 05 '19 at 15:19
  • 1
    @user54287 remove `.GetQueryable()` you do not need it. That looks like one of their samples to demonstrate how to use the library – Nkosi Sep 05 '19 at 15:33
  • @Nkosi, many thanks for your comment. You are right, I did not need `.GetQueyable()`. I was able to step through the line giving me issues. Only issue now is the mock is not returning the `fakeQueryablePUser` object I told it to return. Instead, I'm getting `null` when I step through the code. – user54287 Sep 05 '19 at 15:47
  • 1
    @user54287 Does your fake data have data in it that will match the predicate in the `Where`. Remember `FirstOrDefault` will return `null` if there are no elements. And if the data does not satisfy the filter you will get back null by default. – Nkosi Sep 05 '19 at 15:59
  • @Nkosi, many thanks. You were right. It was the predicate. I'm learning a great deal here. It now returns the value I set up. – user54287 Sep 05 '19 at 18:06
  • @Nkosi; to gain the point, since you helped. Would you want to make an answer and I mark as the solution or you are happy for me to write it out (as it may come in handy for others)? – user54287 Sep 05 '19 at 18:17
  • @user54287 I've transferred my comments that helped into an answer as requested. – Nkosi Sep 05 '19 at 18:26

2 Answers2

2

The initial issue is as you suspected with FirstOrDefaultAsync. That extension expects the queryable to also have a IAsyncQueryProvider in order to match async EF which the mock wont be by default.

Remove .GetQueryable() as you do not need it. That member came from one of the samples to demonstrate how to use the mock library.

Also make sure the fake data has data in it that will match the predicate in the Where.

.Where(p => p.UserId == user.Id && p.Portal.PortalType == dto.PortalType)

FirstOrDefault will return null if there are no elements to enumerate.

And if the data does not satisfy the filter you will get back null by default.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

You dont, IQueryable<T> is an implementation detail handled by Entity Framework / Core. Unless your business logic actually creates an implementation of IQueryable<T> then you want to return a mock stub object.

i.e (Note that this uses the library Moq to mock the objects as im not sure what you're using and the implementation may vary.)

_mockedEntityQuery = new Mock<IQueryable<T>>();
_portalUserRepositoryMock = Substitute.For<IPortalUserRepository>();
_portalUserRepositoryMock.GetAll().Returns(_mockedEntityQuery.Object);

If you return an instance of IQueryable<T> you would also be testing that implementation which will have already been done by the EF unit tests. Unit tests should only test the code that is within the scope of the unit.

Kieran Devlin
  • 1,373
  • 1
  • 12
  • 28