簡體   English   中英

ASP.NET Core 中 AsNoTracking 的模擬或更好的解決方法

[英]Mock or better workaround for AsNoTracking in 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;
    }
}

我的測試:

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) 中拋出的異常是:

異常消息:

類型“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

我的嘗試:

嘗試像stackoverflow 中建議的那樣模擬 AsNoTracking: mock-asnotracking-entity-framework

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 是 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;
    }
}

所以,我的問題:

  1. 使用我的解決方法是測試這樣的東西的唯一方法嗎?
  2. 有沒有可能模擬這個?
  3. Check.NotNull(source, nameof(source)); 是什么? 在 AsNoTracking() 中做什么?

不要模擬 DataContext。

DataContext 是訪問層的實現細節。 Entity Framework Core 提供了兩個選項,用於在沒有實際數據庫的情況下編寫具有 DataContext 依賴項的測試。

In-Memory 數據庫 - 使用 InMemory 進行測試

內存中的 SQLite - 使用 SQLite 進行測試

為什么不應該模擬 DataContext?
僅僅因為使用模擬的DataContext您將只測試按預期順序調用的方法。
相反,在測試中,您應該測試代碼的行為、返回值、更改的狀態(數據庫更新)。
當您測試行為時,您將能夠重構/優化您的代碼,而無需為代碼中的每次更改重寫測試。

萬一內存測試沒有提供所需的行為 - 針對實際數據庫測試您的代碼。

老問題,但有些人一直遇到麻煩(就像幾個小時前的我一樣!)。 我只會從官方文檔中做一個簡短的解釋,這些文檔很好地涵蓋了單元測試數據庫。 .AsNoTracking()是一種查詢功能,如其源代碼所示:

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

好吧,正如 MSDN 中提到的:

但是,無法正確模擬 DbSet 查詢功能,因為查詢是通過 LINQ 運算符表示的,這些運算符是對 IQueryable 的靜態擴展方法調用。 因此,當有些人談論“模擬 DbSet”時,他們真正的意思是他們創建了一個由內存中集合支持的 DbSet,然后在內存中針對該集合評估查詢運算符,就像一個簡單的 IEnumerable。 而不是模擬,這實際上是一種假的,其中內存中的集合替換了真實的數據庫。

還有其他方法可以對 EF Core 進行單元測試。 在內存中使用 SQLite 是最好的方法之一。 請閱讀這個主題,這對許多情況都非常有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM