NSubstitute DbSet / IQueryable<T>
Asked Answered
P

7

42

So EntityFramework 6 is a lot better testable then previous versions. And there are some nice examples on the internet for frameworks like Moq, but the case is, I prefer using NSubstitute. I've got the "non-query" examples translated to work with the use of NSubstitute, but I can't get my head around the 'query-test'.

How does Moq's items.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider); translate to NSubstitute? I thought something like ((IQueryable<T>) items).Provider.Returns(data.Provider); but that didn't work. I've also tried items.AsQueryable().Provider.Returns(data.Provider); but that didn't work either.

The exeption I'm getting is:

"System.NotImplementedException : The member 'IQueryable.Provider' has not been implemented on type 'DbSet1Proxy' which inherits from 'DbSet1'. Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used."

So let me quote the code example from the link above. This code sample uses Moq to mock the DbContext and DbSet.

public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = new Mock<DbSet<Blog>>();
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

  var mockContext = new Mock<BloggingContext>();
  mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);

  // ...
}

And this is how far I come with NSubstitute

public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = Substitute.For<DbSet<Blog>>();
  // it's the next four lines I don't get to work
  ((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);
  ((IQueryable<Blog>) mockSet).Expression.Returns(data.Expression);
  ((IQueryable<Blog>) mockSet).ElementType.Returns(data.ElementType);
  ((IQueryable<Blog>) mockSet).GetEnumerator().Returns(data.GetEnumerator());

  var mockContext = Substitute.For<BloggingContext>();
  mockContext.Blogs.Returns(mockSet);

  // ...
}

So the question is; How does one Substitute a property of IQueryable (like Provider)?

Pearlene answered 12/1, 2014 at 0:57 Comment(2)
UPDATED: Use EntityFramework.Testing.NSubstitute package that provides an implementation of DbAsyncQueryProvider.Corticosterone
In EF Core consider in-memory providers learn.microsoft.com/en-us/ef/core/testingPresbyopia
R
44

This happens because of NSubstitute syntax specific. For example in:

((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);

NSubstitute calls the Provider's getter, then it specifies the return value. This getter call isn't intercepted by the substitute and you get an exception. It happens because of explicit implementation of IQueryable.Provider property in DbQuery class.

You can explicitly create substitutes for multiple interfaces with NSub, and it creates a proxy which covers all specified interfaces. Then calls to the interfaces will be intercepted by the substitute. Please use the following syntax:

// Create a substitute for DbSet and IQueryable types:
var mockSet = Substitute.For<DbSet<Blog>, IQueryable<Blog>>();
    
// And then as you do:
((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider);
((IQueryable<Blog>) mockSet).Expression.Returns(data.Expression);
((IQueryable<Blog>) mockSet).ElementType.Returns(data.ElementType);
((IQueryable<Blog>) mockSet).GetEnumerator().Returns(data.GetEnumerator());
Rosalindrosalinda answered 12/1, 2014 at 14:6 Comment(4)
Thanks! This is really helpful information. I'm not sure yet if this is the best way to go for substituting DbSet (as it turned out using IDbSet solves the problems), but it sure is helpful in other situations.Pearlene
I'm marking you as the answer, because your suggestion is the solution for supporting Async (Substitute.For<IDbSet<Blog>, IDbAsyncEnumerable<Blog>>()). That is not really the problem of the question asked, but it lies in the extension of it. Thanks again for this insight!Pearlene
Tip: make sure your DbSets in your context are virtualJiffy
Is there an elegant solution to handle .Include() issues? I keep getting source is null errors.Haigh
P
19

Thanks to Kevin, I've found the problem in my code translation.

The unittest code samples are mocking DbSet, but NSubstitute requires the interface implementation. So the equivalent of Moqs new Mock<DbSet<Blog>>() for NSubstitute is Substitute.For<IDbSet<Blog>>(). You're not always required to provide the Interface, so that's why I was confused. But in this specific case, it turned out to be crucial.

It also turned out that we don't have to cast to Queryable when using the interface IDbSet.

So the working test code:

public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
    new Blog { Name = "BBB" },
    new Blog { Name = "ZZZ" },
    new Blog { Name = "AAA" },
  }.AsQueryable();

  var mockSet = Substitute.For<IDbSet<Blog>>();
  mockSet.Provider.Returns(data.Provider);
  mockSet.Expression.Returns(data.Expression);
  mockSet.ElementType.Returns(data.ElementType);
  mockSet.GetEnumerator().Returns(data.GetEnumerator());

  var mockContext = Substitute.For<BloggingContext>();
  mockContext.Blogs.Returns(mockSet);

  // Act and Assert ...
}

I've written a small extention method to cleanup the Arrange section of the unit tests.

public static class ExtentionMethods
{
    public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
    {
        dbSet.Provider.Returns(data.Provider);
        dbSet.Expression.Returns(data.Expression);
        dbSet.ElementType.Returns(data.ElementType);
        dbSet.GetEnumerator().Returns(data.GetEnumerator());
        return dbSet;
    }
}

// usage like:
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data);

Not the question, but in case you also need to be able to support async operations:

public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
{
  dbSet.Provider.Returns(data.Provider);
  dbSet.Expression.Returns(data.Expression);
  dbSet.ElementType.Returns(data.ElementType);
  dbSet.GetEnumerator().Returns(data.GetEnumerator());

  if (dbSet is IDbAsyncEnumerable)
  {
    ((IDbAsyncEnumerable<T>) dbSet).GetAsyncEnumerator()
      .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));
    dbSet.Provider.Returns(new TestDbAsyncQueryProvider<T>(data.Provider));
  }

  return dbSet;
}

// create substitution with async
var mockSet = Substitute.For<IDbSet<Blog>, IDbAsyncEnumerable<Blog>>().Initialize(data);
// create substitution without async
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data);
Pearlene answered 12/1, 2014 at 12:34 Comment(5)
It's ok.But what is TestDbAsyncEnumerator and TestDbAsyncQueryProvider.Blackwell
It's an inmemory db query provider, as explained by the link I'm providing in my question, as well as this answer. Take a look at the msdn page: msdn.microsoft.com/nl-nl/data/dn314429.aspx#asyncPearlene
I have tried this but get an exception: NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException: Can not return value of type IDbSet1Proxy for BloggingContext.get_Blogs (expected type DbSet1). The Context was generated by the EF TT templateFrown
@Frown Had the same problem, you can make it work by adjusting the interfaces/types in the for call: Substitute.For<IDbSet<DataModel>, DbSet<DataModel>>();Photofluorography
@Frown you can also modify your BloggingContext class: public virtual IDbSet<Blog> Blogs { get; set; } public virtual IDbSet<Post> Posts { get; set; }Fernyak
C
8

This is my static generic static method to generate fake DbSet. It may by useful.

 public static class CustomTestUtils
{
    public static DbSet<T> FakeDbSet<T>(List<T> data) where T : class
    {
        var _data = data.AsQueryable();
        var fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
        ((IQueryable<T>)fakeDbSet).Provider.Returns(_data.Provider);
        ((IQueryable<T>)fakeDbSet).Expression.Returns(_data.Expression);
        ((IQueryable<T>)fakeDbSet).ElementType.Returns(_data.ElementType);
        ((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(_data.GetEnumerator());

        fakeDbSet.AsNoTracking().Returns(fakeDbSet);

        return fakeDbSet;
    }

}
Consignment answered 27/8, 2015 at 11:25 Comment(2)
I think that missing the keyword "this" from the definition of the argument "data"Pentateuch
Used this in several projects. I like that it removes the plumbing from my testsJena
R
3

I wrote a wrapper about a year ago around the same code you are referencing from Testing with Your Own Test Doubles (EF6 onwards). This wrapper can be found on GitHub DbContextMockForUnitTests. The purpose of this wrapper is to reduce the amount of repetitive/duplicate code needed to setup unit tests that make use of EF where you want to mock that DbContext and DbSets. Most of the mock EF code you have in the OP can reduced down to a 2 lines of code (and only 1 if you are using DbContext.Set<T> instead of DbSet properties) and the mock code is then called in the wrapper.

To use it copy and include the files in folder MockHelpers to your Test project.

Here is an example test using what you had above, notice that there is now only 2 Lines of code are needed to setup the mock DbSet<T> on the mocked DbContext.

public void GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  };

  var mockContext = Substitute.For<BloggingContext>();

  // Create and assign the substituted DbSet
  var mockSet = data.GenerateMockDbSet();
  mockContext.Blogs.Returns(mockSet);

  // act
}

It is just as easy to make this a test that invokes something that uses the async/await pattern like .ToListAsync() on the DbSet<T>.

public async Task GetAllBlogs_orders_by_name()
{
  // Arrange
  var data = new List<Blog>
  {
     new Blog { Name = "BBB" },
     new Blog { Name = "ZZZ" },
     new Blog { Name = "AAA" },
  };

  var mockContext = Substitute.For<BloggingContext>();

  // Create and assign the substituted DbSet
  var mockSet = data.GenerateMockDbSetForAsync(); // only change is the ForAsync version of the method
  mockContext.Blogs.Returns(mockSet);

  // act
}
Rebate answered 14/9, 2016 at 11:33 Comment(0)
U
1

Using MockQueryable.NSubstitute it is very easy to mock DbSet.

You can do it as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Fridge.API.Core.Entities;
using Fridge.API.Core.Interfaces.Persistence;
using Fridge.Application.Features.UserManagement.Queries.GetUser;
using Microsoft.EntityFrameworkCore;
using MockQueryable.NSubstitute;
using NSubstitute;
using NSubstitute.Core;
using NUnit.Framework;

namespace Fridge.Application.UnitTests.Features.UserManagement.Queries.GetUser;

[TestFixture]
public class GetUserQueryHandlerTests
{
    private IAppDbContext _dbContext = null!;
    private IMapper _mapper = null!;

    private GetUserQueryHandler _handler = null!;


    [SetUp]
    protected void BeforeEach()
    {
        _dbContext = Substitute.For<IAppDbContext>();
        _mapper = Substitute.For<IMapper>();

        _handler = new GetUserQueryHandler(_dbContext, _mapper);
    }

    [Test]
    public async Task Handler_WhenUserDoesNotExists_ReturnsResponseFailure()
    {
        // Arrange
        var users = Array.Empty<User>().AsQueryable();
        var usersDbSet = users.BuildMockDbSet();
        _dbContext.Users.Returns(usersDbSet);

        // Act
        var response = await _handler.Handle(new GetUserQuery(Guid.NewGuid()), CancellationToken.None);

        // Assert
        Assert.Multiple(() =>
        {
            Assert.That(response.Succeed, Is.False);
            Assert.That(response.Value, Is.Null);
            Assert.That(response.Error.Code, Is.EqualTo((int)HttpStatusCode.NotFound));
            Assert.That(response.Error.Message, Is.EqualTo("Could not found user"));
            Assert.That(response.Error.Name, Is.EqualTo(nameof(HttpStatusCode.NotFound)));
        });
    }
}

I wanted to mock an empty array but you can fill it with entries and mock return data the same way.

Universe answered 3/7, 2023 at 14:1 Comment(0)
H
0

You shouldn't need to mock all of the pieces of the IQueryable. When I use NSubstitute for mocking an EF DbContext I do something like so:

interface IContext
{
  IDbSet<Foo> Foos { get; set; }
}

var context = Substitute.For<IContext>();

context.Foos.Returns(new MockDbSet<Foo>());

With a simple implementation of IDbSet around a list or something for my MockDbSet().

In general you should be mocking interfaces, not types as NSubstitute will only override virtual methods.

Hexapody answered 12/1, 2014 at 1:15 Comment(2)
In EF 6 DbSet properties should be made virtual for testability. I dont want to create a fake DbSet wrapper. Substituting these 4 properties should be easier and better than creating the wrapper you're proposing. So your suggestion isn't really the answer I'm looking for.Pearlene
Judging by the error message I'd say that DbSet proxy created by NSubstitute does not support an explicitly implemented interface, only the specific contract or object you're substituting. This is kind of born out in (github.com/nsubstitute/NSubstitute/issues/95) and may be a limitation of the framework.Hexapody
J
0

When you use something like

MyDbContext.CounterpartyDbSet.AsQuariable()    // or AsNoTracking()
    .bla().bla().bla()

you can walk by simple way:

var counterpartyList = new List<Counterparty>()
{ 
    // some items here;
}

var myDbContext = Substitute.For<IMyDbContext>();
var counterpartySet = Substitute.For<DbSet<Counterparty>>();
counterpartySet.AsQueryable()    // or AsNoTracking()
    .Returns(counterpartyList.AsQueryable());
myDbContext.CounterpartyDbSet.Returns(counterpartySet);
Jenijenica answered 9/12, 2022 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.