Pix*_*ord 6 c# linq unit-testing asp.net-core
您如何模拟 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;
}
}
Run Code Online (Sandbox Code Playgroud)
我的测试:
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;
}
Run Code Online (Sandbox Code Playgroud)
每次我运行这个测试时,在 AsNoTracking() 的 isContact(String firstName, String lastName) 中抛出的异常是:
异常。消息:
类型“Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions”上没有与指定参数匹配的方法“AsNoTracking”
异常.堆栈跟踪:
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
Run Code Online (Sandbox Code Playgroud)
我的尝试:
尝试像stackoverflow中建议的那样模拟AsNoTracking :mock-asnotracking-entity-framework:
mockSet.As<IQueryable<T>>().Setup(m => m.AsNoTracking<T>())
.Returns(mockSet.Object);
Run Code Online (Sandbox Code Playgroud)
在 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;
}
Run Code Online (Sandbox Code Playgroud)
由于我在测试期间提供的模拟 DbSet<> 提供者是 IQueryable 函数 AsNoTracking 应该返回输入源,因为“source.Provider is EntityQueryProvider”是假的。
我唯一无法检查的是 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;
}
}
Run Code Online (Sandbox Code Playgroud)
所以,我的问题:
不要模拟 DataContext。
DataContext是访问层的实现细节。Entity Framework Core 提供了两个选项,用于在没有实际数据库的情况下使用 DataContext 依赖项编写测试。
内存数据库 -使用 InMemory 进行测试
SQLite 内存中 -使用 SQLite 进行测试
为什么你不应该嘲笑 DataContext?
仅仅是因为使用模拟,DataContext
您将仅测试按预期顺序调用的方法。
相反,在测试中,您应该测试代码的行为、返回的值、更改的状态(数据库更新)。
当您测试行为时,您将能够重构/优化代码,而无需为代码中的每个更改重写测试。
如果内存测试未提供所需的行为 - 根据实际数据库测试您的代码。
归档时间: |
|
查看次数: |
1332 次 |
最近记录: |