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
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With