ASP.NET Core 中 AsNoTracking 的模拟或更好的解决方法

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)

所以,我的问题:

  1. 我的解决方法是测试此类内容的唯一方法吗?
  2. 有没有可能嘲笑这个?
  3. 什么 Check.NotNull(source, nameof(source)); 在 AsNoTracking() 中做什么?

Fab*_*bio 2

不要模拟 DataContext。

DataContext是访问层的实现细节。Entity Framework Core 提供了两个选项,用于在没有实际数据库的情况下使用 DataContext 依赖项编写测试。

内存数据库 -使用 InMemory 进行测试

SQLite 内存中 -使用 SQLite 进行测试

为什么你不应该嘲笑 DataContext?
仅仅是因为使用模拟,DataContext您将仅测试按预期顺序调用的方法。
相反,在测试中,您应该测试代码的行为、返回的值、更改的状态(数据库更新)。
当您测试行为时,您将能够重构/优化代码,而无需为代码中的每个更改重写测试。

如果内存测试未提供所需的行为 - 根据实际数据库测试您的代码。