34

How do I mock AsNoTracking method?
In below example, DbContext has injected to the service class.It works fine if I remove AsNoTracking extension method from GetOrderedProducts method, but with AsNoTracking test fails because it returns null. I've also tried to mock AsNoTracking to return proper value but it didn't work.

public interface IUnitOfWork
{
    IDbSet<TEntity> Set<TEntity>() where TEntity : class;
    int SaveAllChanges();
}

public class Entites : DbContext, IUnitOfWork
{
    public virtual DbSet<Product> Products { get; set; }  // This is virtual because Moq needs to override the behaviour

    public new virtual IDbSet<TEntity> Set<TEntity>() where TEntity : class   // This is virtual because Moq needs to override the behaviour 
    {
        return base.Set<TEntity>();
    }

    public int SaveAllChanges()
    {
        return base.SaveChanges();
    }
}

    public class ProductService
{
    private readonly IDbSet<Product> _products;
    private readonly IUnitOfWork _uow;

    public ProductService(IUnitOfWork uow)
    {
        _uow = uow;
        _products = _uow.Set<Product>();
    }
    public IEnumerable<Product> GetOrderedProducts()
    {
        return _products.AsNoTracking().OrderBy(x => x.Name).ToList();
    }
}

    [TestFixture]
public class ProductServiceTest
{
    private readonly ProductService _productService;

    public ProductServiceTest()
    {
        IQueryable<Product> data = GetRoadNetworks().AsQueryable();
        var mockSet = new Mock<DbSet<Product>>();
        mockSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
        var context = new Mock<Entites>();
        context.Setup(c => c.Products).Returns(mockSet.Object);
        context.Setup(m => m.Set<Product>()).Returns(mockSet.Object);
        context.Setup(c => c.Products.AsNoTracking()).Returns(mockSet.Object);
        _productService = new ProductService(context.Object);
    }

    private IEnumerable<Product> GetRoadNetworks()
    {
        return new List<Product>
        {
            new Product
            {
                Id = 1,
                Name = "A"
            },
            new Product
            {
                Id = 2,
                Name = "B"
            },
            new Product
            {
                Id = 1,
                Name = "C"
            }
        };
    }

    [Test]
    public void GetOrderedProductTest()
    {
        IEnumerable<Product> products = _productService.GetOrderedProducts();
        List<string> names = products.Select(x => x.Name).ToList();
        var expected = new List<string> {"A", "B", "C"};
        CollectionAssert.AreEqual(names, expected);
    }
}

The problem is AsNoTracking returns null in unit test enter image description here

Shahin
  • 12,543
  • 39
  • 127
  • 205

3 Answers3

69

Looking at the source code of the AsNoTracking() extension method:

public static IQueryable AsNoTracking(this IQueryable source)
{
    var asDbQuery = source as DbQuery;
    return asDbQuery != null ? asDbQuery.AsNoTracking() : CommonAsNoTracking(source);
}

Since source (your DbSet<Product> you're trying to mock) is indeed a DbQuery (because DbSet is deriving from DbQuery), it tries to invoke the 'real' (non-mocked) AsNoTracking() method which rightfully returns null.

Try to mock the AsNoTracking() method as well:

mockSet.Setup(x => x.AsNoTracking()).Returns(mockSet.Object);
haim770
  • 48,394
  • 7
  • 105
  • 133
  • 4
    This is not working on dotnet/EF core: Expression references a method that does not belong to the mocked object: x => x.AsNoTracking(). I guess because AsNoTracking() is not on IQueryable anymore but instead on 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions'? Any idea about a solution? – Bassebus Jun 12 '17 at 08:29
  • @Bassebus, I'm not familiar enough with EF core to answer the question but it might deserve a new question – haim770 Jun 12 '17 at 08:43
  • okay, thanks. I did some more googling and think this might be the problem/solution https://github.com/aspnet/EntityFramework/issues/7937 – Bassebus Jun 12 '17 at 08:49
  • So what did you use as the final solution? – Paul Gorbas Jun 22 '17 at 17:05
  • For EF Core, please try my library [here](https://github.com/huysentruitw/entity-framework-core-mock). I've also created an EF6 version [here](https://github.com/huysentruitw/entity-framework-mock). – huysentruitw Oct 24 '18 at 17:33
  • What is the type of mockSet here? – Triynko Aug 26 '20 at 19:55
  • @Triynko, According to the original code by the OP, it's `Mock>` (using https://www.nuget.org/packages/Moq/) – haim770 Aug 26 '20 at 20:06
6

You have:

context.Setup(c => c.Products).Returns(mockSet.Object);
context.Setup(m => m.Set<Product>()).Returns(mockSet.Object);
context.Setup(c => c.Products.AsNoTracking()).Returns(mockSet.Object);

But remember that extension methods are just syntactic sugar. So:

c.Products.AsNoTracking()

is really just:

System.Data.Entity.DbExtensions.AsNoTracking(c.Products)

therefore your mock setup above is meaningless.

The question is what the static DbExtensions.AsNoTracking(source) method actually does to its argument. Also see the thread What difference does .AsNoTracking() make?

What happens if you just remove the Setup involving AsNoTracking from your test class?

It might be helpful to give all your mocks MockBehavior.Strict. In that case you will discover if the members the static method invokes on them, are mockable by Moq (i.e. virtual methods/properties in a general sense). Maybe you can mock the non-static method DbQuery.AsNoTracking if necessary.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Thank you, I need to use AsNoTracking in order to improve performance of the query. I am looking for a way to fix this null exception problem when I use AsNoTracking in service classes. – Shahin Nov 23 '14 at 08:57
  • In my EF (version 6.0) it is not always an extension method but a virtual method defined in `DbQuery<>`. Only when you refer to your data as `IQueryable<>` then you'll have to use the extension method. – haim770 Nov 23 '14 at 09:20
  • 1
    @haim770 You are absolutely right. Your answer is much more precise than mine was, although I did hint at the non-static method on `DbQuery<>` in the last sentence. I will repeat that if he had used `Strict` mocks he would have got an exception telling him what method he needed to mock, instead of just waiting until the `NullReferenceException` arose. People use loose mocks too much. – Jeppe Stig Nielsen Nov 23 '14 at 21:35
  • @JeppeStigNielsen, Thanks. I don't know much about strict vs. loose mocking. But out of curiosity, had he used strict mocks when exactly can we except the exception to be thrown? – haim770 Nov 24 '14 at 08:24
  • @haim770 With a strict mock, as soon as some member that was not `Setup`, is called, an exception is thrown saying "all invocations must have a corresponding setup". So if type `T` has a method `int Meth()`, and `Meth` is virtual enough that Moq can control its implementation, then an example is: `var m = new Mock(MockBehavior.Strict); /* no setup for Meth */ m.Object.Meth(); /* throws an exception */` In contrast with a loose mock, this will simply return `default(int)`, or `0`. (In the scenario of the question it returned `default(DbQuery)`, or `null`.) – Jeppe Stig Nielsen Nov 24 '14 at 12:25
-2

you can use Entity Framework Effort to mock the AsNoTracking(), also you can mock the Db Transactions and Entity State by using Effort - Official site for Effort

Vinod L
  • 17
  • 3