我怎么能模拟 FromSql() 方法? [英] How could I Mock the FromSql() method?

查看:17
本文介绍了我怎么能模拟 FromSql() 方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道除了构建一个包装器来模拟 FromSql 之外还有什么方法吗?我知道这个方法是静态的,但由于他们在实体框架核心中添加了诸如 AddEntityFrameworkInMemoryDatabase 之类的东西,我认为这也可能有一个解决方案,我在我的项目中使用了 EF Core 1.0.1.>

我的最终目标是测试这个方法:

public List句柄(ClosestLocationsQuery 消息){返回 _context.ClosestLocations.FromSql("EXEC GetClosestLocations {0}, {1}, {2}, {3}",message.LocationQuery.Latitude,消息.位置查询.经度,message.LocationQuery.MaxRecordsToReturn ??10、message.LocationQuery.Distance ??10).ToList();}

基于这个答案,我想确保我的查询是用我传递给它的同一个对象处理的在实体框架 6 中,我可以这样做:

[事实]公共无效 HandleInvokesGetClosestLocationsWithCorrectData(){var message = new ClosestLocationsQuery{位置查询 =新位置查询 {距离 = 1,纬度 = 1.165,经度 = 1.546,MaxRecordsToReturn = 1}};var dbSetMock = new Mock>();dbSetMock.Setup(m => m.FromSql(It.IsAny(), message)).Returns(It.IsAny>());var contextMock = new Mock();contextMock.Setup(c => c.Set()).Returns(dbSetMock.Object);var sut = new ClosestLocationsQueryHandler(contextMock.Object);var 结果 = sut.Handle(message);contextMock.Verify(x => x.ClosestLocations.FromSql(It.IsAny(), It.Is(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 不同,EF Core 中的 FromSql 是静态扩展方法,我问这个问题是因为我认为我可能会从错误的角度解决这个问题,或者可能有比包装更好的替代方案,我很感激对此有任何想法.

解决方案

我也遇到了同样的情况,Philippe 给出的回答有所帮助,但主要方法是抛出 System.ArgumentNullException.

来自此链接,我终于可以写一些单元测试了...

这是我正在测试的课程:

公共类HolidayDataAccess : IHolidayDataAccess{私有只读 IHolidayDataContext holDataContext;公共假期数据访问(IHolidayDataContext holDataContext){this.holDataContext = holDataContext;}公共异步任务>GetHolidayDates(DateTime startDate, DateTime endDate){使用 (this.holDataContext){IList日期 = 等待 holDataContext.Dates.FromSql($"[dba].[usp_GetHolidayDates] @StartDate = {startDate}, @EndDate = {endDate}").AsNoTracking().ToListAsync();返回日期;}}}

这里是测试方法:

[测试方法]公共异步任务 GetHolidayDates_Should_Only_Return_The_Dates_Within_Given_Range(){//安排.SpAsyncEnumerableQueryable日期 = 新 SpAsyncEnumerableQueryable();日期.添加(新假日日期(){日期=新日期时间(2018,05,01)});日期.添加(新假日日期(){日期=新日期时间(2018年,07,01)});日期.Add(new HolidayDate() { Date = new DateTime(2018, 04, 01) });日期.添加(新假日日期(){日期=新日期时间(2019年,03,01)});日期.Add(new HolidayDate() { Date = new DateTime(2019, 02, 15) });var options = new DbContextOptionsBuilder().UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()).选项;HolidayDataContext context = new HolidayDataContext(options);context.Dates = context.Dates.MockFromSql(dates);HolidayDataAccess dataAccess = new HolidayDataAccess(context);//行为.IEnumerableresutlDates = await dataAccess.GetHolidayDates(new DateTime(2018, 01, 01), new DateTime(2018, 05, 31));//断言.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);//我们不需要调用它,因为我们正在为上下文使用 using 块...//context.Database.EnsureDeleted();}

要使用 UseInMemoryDatabase,您需要添加来自 NuGet助手类在这里:

公共类 SpAsyncEnumerableQueryable: IAsyncEnumerable, IQueryable;{私有只读 IListListOfSpReocrds;公共类型元素类型 =>抛出新的 NotImplementedException();公共 IQueryProvider 提供者 =>new Mock().Object;表达式 IQueryable.Expression =>抛出新的 NotImplementedException();公共 SpAsyncEnumerableQueryable(){this.listOfSpReocrds = new List();}public void Add(T spItem)//这是添加的新方法以允许 xxx.Add(new T) 样式添加 sp 记录...{this.listOfSpReocrds.Add(spItem);}公共 IEnumerator获取枚举器(){返回 this.listOfSpReocrds.GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){返回 GetEnumerator();}IAsyncEnumeratorIAsyncEnumerable.GetEnumerator(){返回 listOfSpReocrds.ToAsyncEnumerable().GetEnumerator();}}

...以及包含 FromSql 方法模拟的 Db 扩展类...

公共静态类 DbSetExtensions{公共静态 DbSet<T>MockFromSql(this DbSet dbSet, SpAsyncEnumerableQueryable spItems) where T : class{var queryProviderMock = new Mock();queryProviderMock.Setup(p => p.CreateQuery(It.IsAny())).Returns(x => spItems);var dbSetMock = new Mock>();dbSetMock.As>().SetupGet(q => q.Provider).Returns(() => queryProviderMock.Object);dbSetMock.As>().Setup(q => q.Expression).Returns(Expression.Constant(dbSetMock.Object));返回 dbSetMock.Object;}}

希望这有帮助!

编辑:重构 SpAsyncEnumerableQueryable 类以具有 Add 方法.摆脱了采用 T 数组的参数化构造.实现了 IQueryProvider Provider =>新的 Mock().Object; 以支持 .AsNoTracking().异步调用 ToList.

I was wondering is there any way other than building a wrapper for mocking the FromSql? I know this method is static, but since they added things like AddEntityFrameworkInMemoryDatabase to entity framework core, I thought there might be a solution for this too, I use EF Core 1.0.1 in my project.

My end goal is to test this method:

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();
}

I want to ensure that my query is handled with the same object that I passed into it, based on this answer in entity framework 6 I could do something like this:

[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)));
}

But unlike SqlQuery<T> in EF 6, the FromSql<T> in EF Core is static extension method, I'm asking this question because I think I might approach this problem from the wrong angle or there might be a better alternative than a wrapper, I'd appreciate any thought on this.

解决方案

I also fell into the same situation and answer given by Philippe helped but it the main method was throwing System.ArgumentNullException.

From this link, I was finally able to write some unit tests...

Here is my class under test:

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

and here is the test method:

[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();
}

To use UseInMemoryDatabase you need to add Microsoft.EntityFrameworkCore.InMemory package from NuGet The helper classes are here:

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();
    }
}

...and the Db extensions class that contains the mock of FromSql method..

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

Hope this helps!

Edits: refactored SpAsyncEnumerableQueryable class to have Add method. Got rid of parameterised construction that took array of T. Implemented IQueryProvider Provider => new Mock<IQueryProvider>().Object; to support .AsNoTracking(). Calling the ToList asynchronously.

这篇关于我怎么能模拟 FromSql() 方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆