どのようにしてFromSql()メソッドをモックできますか?

c# entity-framework-core moq unit-testing

質問

私はFromSqlを嘲笑するためのラッパーを構築する以外の方法があるのだろうかと思っていFromSqlか?私はこのメソッドが静的であることは知っていますが、エンティティフレームワークのコアにAddEntityFrameworkInMemoryDatabaseようなものを追加して以来、私はプロジェクトの中でEF Core 1.0.1を使用しています。

私の最終目標はこのメソッドをテストすることです:

public List<Models.ClosestLocation> Handle(ClosestLocationsQuery message)
{
    return _context.ClosestLocations.FromSql(
        "EXEC GetClosestLocations {0}, {1}, {2}, {3}",
        message.LocationQuery.Latitude,
        message.LocationQuery.Longitude,
        message.LocationQuery.MaxRecordsToReturn ?? 10,
        message.LocationQuery.Distance ?? 10
    ).ToList();
}

エンティティのフレームワーク6でこの答えに基づいて、私が渡したのと同じオブジェクトでクエリが処理されるようにしたいと思います。

[Fact]
public void HandleInvokesGetClosestLocationsWithCorrectData()
{
    var message = new ClosestLocationsQuery
    {
        LocationQuery =
            new LocationQuery {Distance = 1, Latitude = 1.165, Longitude = 1.546, MaxRecordsToReturn = 1}
    };

    var dbSetMock = new Mock<DbSet<Models.ClosestLocation>>();

    dbSetMock.Setup(m => m.FromSql(It.IsAny<string>(), message))
        .Returns(It.IsAny<IQueryable<Models.ClosestLocation>>());

    var contextMock = new Mock<AllReadyContext>();

    contextMock.Setup(c => c.Set<Models.ClosestLocation>()).Returns(dbSetMock.Object);

    var sut = new ClosestLocationsQueryHandler(contextMock.Object);
    var results = sut.Handle(message);

    contextMock.Verify(x => x.ClosestLocations.FromSql(It.IsAny<string>(), It.Is<ClosestLocationsQuery>(y =>
        y.LocationQuery.Distance == message.LocationQuery.Distance &&
        y.LocationQuery.Latitude == message.LocationQuery.Latitude &&
        y.LocationQuery.Longitude == message.LocationQuery.Longitude &&
        y.LocationQuery.MaxRecordsToReturn == message.LocationQuery.MaxRecordsToReturn)));
}

しかし、EF 6のSqlQuery<T>とは異なり、EFコアのFromSql<T>は静的な拡張メソッドです。私は間違った角度からこの問題に近づくかもしれないと思うので、この質問をしています。ラッパー、私はこれについて何か考えていただければ幸いです。

人気のある回答

私も同じ状況に落ちて、Philippeによって与えられた答えは助けましたが、それは主な方法がSystem.ArgumentNullExceptionを投げていたことです。

このリンクから、私は最終的にいくつかのユニットテストを書くことができました...

テスト中のクラスがあります:

public class HolidayDataAccess : IHolidayDataAccess
{
    private readonly IHolidayDataContext holDataContext;

    public HolidayDataAccess(IHolidayDataContext holDataContext)
    {
        this.holDataContext = holDataContext;
    }

    public async Task<IEnumerable<HolidayDate>> GetHolidayDates(DateTime startDate, DateTime endDate)
    {
        using (this.holDataContext)
        {
            IList<HolidayDate> dates = await holDataContext.Dates.FromSql($"[dba].[usp_GetHolidayDates] @StartDate = {startDate}, @EndDate = {endDate}").AsNoTracking().ToListAsync();
            return dates;
        }
    }
}

ここにテストメソッドがあります:

[TestMethod]
public async Task GetHolidayDates_Should_Only_Return_The_Dates_Within_Given_Range()
{
    // Arrange.

        SpAsyncEnumerableQueryable<HolidayDate> dates = new SpAsyncEnumerableQueryable<HolidayDate>();
        dates.Add(new HolidayDate() { Date = new DateTime(2018, 05, 01) });
        dates.Add(new HolidayDate() { Date = new DateTime(2018, 07, 01) });
        dates.Add(new HolidayDate() { Date = new DateTime(2018, 04, 01) });
        dates.Add(new HolidayDate() { Date = new DateTime(2019, 03, 01) });
        dates.Add(new HolidayDate() { Date = new DateTime(2019, 02, 15) });


    var options = new DbContextOptionsBuilder<HolidayDataContext>()
        .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
        .Options;

    HolidayDataContext context = new HolidayDataContext(options);

    context.Dates = context.Dates.MockFromSql(dates);

    HolidayDataAccess dataAccess = new HolidayDataAccess(context);

    //Act.
    IEnumerable<HolidayDate> resutlDates = await dataAccess.GetHolidayDates(new DateTime(2018, 01, 01), new DateTime(2018, 05, 31));

    // Assert.

    Assert.AreEqual(resutlDates.Any(d => d.Date != new DateTime(2019, 03, 01)), true);
    Assert.AreEqual(resutlDates.Any(d => d.Date != new DateTime(2019, 02, 15)), true);

    // we do not need to call this becuase we are using a using block for the context...
    //context.Database.EnsureDeleted();
}

UseInMemoryDatabaseを使用するには、 NuGetMicrosoft.EntityFrameworkCore.InMemoryパッケージを追加する必要があります。ヘルパークラスは次のとおりです。

public class SpAsyncEnumerableQueryable<T> : IAsyncEnumerable<T>, IQueryable<T>
{
    private readonly IList<T> listOfSpReocrds;

    public Type ElementType => throw new NotImplementedException();

    public IQueryProvider Provider => new Mock<IQueryProvider>().Object;

    Expression IQueryable.Expression => throw new NotImplementedException();

    public SpAsyncEnumerableQueryable()
    {
        this.listOfSpReocrds = new List<T>();
    }        

    public void Add(T spItem) // this is new method added to allow xxx.Add(new T) style of adding sp records...
    {
        this.listOfSpReocrds.Add(spItem);
    }

    public IEnumerator<T> GetEnumerator()
    {
       return this.listOfSpReocrds.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    IAsyncEnumerator<T> IAsyncEnumerable<T>.GetEnumerator()
    {
        return listOfSpReocrds.ToAsyncEnumerable().GetEnumerator();
    }
}

... FromSqlメソッドのモックを含むDb拡張クラス。

public static class DbSetExtensions
{
    public static DbSet<T> MockFromSql<T>(this DbSet<T> dbSet, SpAsyncEnumerableQueryable<T> spItems) where T : class
    {
        var queryProviderMock = new Mock<IQueryProvider>();
        queryProviderMock.Setup(p => p.CreateQuery<T>(It.IsAny<MethodCallExpression>()))
            .Returns<MethodCallExpression>(x => spItems);

        var dbSetMock = new Mock<DbSet<T>>();
        dbSetMock.As<IQueryable<T>>()
            .SetupGet(q => q.Provider)
            .Returns(() => queryProviderMock.Object);

        dbSetMock.As<IQueryable<T>>()
            .Setup(q => q.Expression)
            .Returns(Expression.Constant(dbSetMock.Object));
        return dbSetMock.Object;
    }
}

お役に立てれば!

編集 :Addメソッドを持つリファクタリングされたSpAsyncEnumerableQueryableクラス。 Tの配列を取ったパラメータ化された構造体をIQueryProvider Provider => new Mock<IQueryProvider>().Object; 。実装されたIQueryProvider Provider => new Mock<IQueryProvider>().Object; .AsNoTracking()をサポートする。 ToListを非同期で呼び出します。



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ