您如何模拟AsNoTracking或此问题有更好的解决方法?
示例:
public class MyContext : MyContextBase
{
// Constructor
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
// Public properties
public DbSet<MyList> MyLists{ get; set; }
}
public class MyList
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Blocked { get; set; }
}
public class MyController : MyControllerBase
{
private MyContext ContactContext = this.ServiceProvider.GetService<MyContext>();
public MyController(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
private bool isContact(string firstName, string lastName)
{
try
{
var list = this
.ContactContext
.MyLists
.AsNoTracking() // !!!Here it explodes!!!
.FirstOrDefault(entity => entity.FirstName == firstName && entity.LastName == lastName);
return list != null;
}
catch (Exception exception)
{
throws Exception;
}
return false;
}
}
我的测试:
using Moq;
using Xunit;
[Fact]
[Trait("Category", "Controller")]
public void Test()
{
string firstName = "Bob";
string lastName = "Baumeister";
// Creating a list with the expectad data
var fakeContacts = new MyList[]
{
new MyList() { FirstName = "Ted", LastName = "Teddy" },
new MyList() { PartnerId = "Bob", Email = "Baumeister" }
};
// Mocking the DbSet<MyList>
var dbSet = CreateMockSet(fakeContacts.AsQueryable());
// Setting the mocked dbSet in ContactContext
ContactContext contactContext = new ContactContext(new DbContextOptions<ContactContext>())
{
MyLists = dbSet.Object
};
// Mocking ServiceProvider
serviceProvider
.Setup(s => s.GetService(typeof(ContactContext)))
.Returns(contactContext);
// Creating a controller
var controller = new ContactController(serviceProvider.Object);
// Act
bool result = controller.isContact(firstName, lastName)
// Assert
Assert.True(result);
}
private Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data)
where T : class
{
var queryableData = data.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(queryableData.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(queryableData.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(queryableData.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(queryableData.GetEnumerator());
return mockSet;
}
每次运行此测试时,AsNoTracking()处的isContact(String firstName,String lastName)引发的异常是:
Exception.Message:
类型上没有方法“ AsNoTracking” 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions' 匹配指定的参数
Exception.StackTrace:
at System.Linq.EnumerableRewriter.FindMethod(Type type, String name, ReadOnlyCollection'1 args, Type[] typeArgs)
at System.Linq.EnumerableRewriter.VisitMethodCall(MethodCallExpression m)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.EnumerableQuery'1.GetEnumerator()
at System.Linq.EnumerableQuery'1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at My.Package.Contact.Controller.MyController.isContact(String firstName, String lastName) in C:\Users\source\repos\src\My.Package\My.Package.Contact\Controller\MyController.cs:line 31
我的尝试:
尝试像stackoverflow: mock-asnotracking-entity-framework中所建议的那样模拟AsNoTracking:
mockSet.As<IQueryable<T>>().Setup(m => m.AsNoTracking<T>())
.Returns(mockSet.Object);
导致System.NotSupportedException中出现ASP.NET Core:
'扩展方法上的无效设置:m => m.AsNoTracking()' mockSet.Setup(m => m.AsNoTracking()) .Returns(mockSet.Object);
在AtNoTracking()更好地了解了Microsoft.EntityFrameworkCore EntityFrameworkQueryableExtensions EntityFrameworkCore EntityFrameworkQueryableExtensions.cs之后:
public static IQueryable<TEntity> AsNoTracking<TEntity>(
[NotNull] this IQueryable<TEntity> source)
where TEntity : class
{
Check.NotNull(source, nameof(source));
return
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: AsNoTrackingMethodInfo.MakeGenericMethod(typeof(TEntity)),
arguments: source.Expression))
: source;
}
由于我在测试过程中提供了模拟的DbSet <>,因此Provider是可查询的,因为“ source.Provider是EntityQueryProvider”为false,因此函数AsNoTracking应该返回输入源。
我唯一无法检查的是Check.NotNull(source,nameof(source));既然我找不到它做什么?如果有人对它的解释或代码说明了它的作用,如果可以与我分享,我将不胜感激。
解决方法:
我在互联网上找到的唯一解决方法是来自线程https://github.com/aspnet/EntityFrameworkCore/issues/7937中的@cdwaddell,该线程基本上编写了自己的门控版本的AsNoTracking()。使用变通办法可以成功,但是我不想实施它,因为它似乎没有检查任何东西?
public static class QueryableExtensions
{
public static IQueryable<T> AsGatedNoTracking<T>(this IQueryable<T> source) where T : class
{
if (source.Provider is EntityQueryProvider)
return source.AsNoTracking<T>();
return source;
}
}
所以,我的问题:
答案 0 :(得分:2)
请勿嘲笑DataContext。
DataContext是访问层的实现细节。 Entity Framework Core提供了两个选项来编写具有DataContext依赖关系的测试,而无需实际的数据库。
内存数据库-Testing with InMemory
SQLite内存-Testing with SQLite
为什么不应该模拟DataContext?
仅仅因为使用模拟DataContext
,您将仅测试按预期顺序调用的方法。
相反,在测试中,您应该测试代码的行为,返回的值,更改的状态(数据库更新)。
测试行为时,您将能够重构/优化代码,而无需为代码中的每个更改重写测试。
如果内存中测试未提供所需的行为,请针对实际数据库测试代码。