如何在Moq实体框架中调用SqlQuery

问题描述 投票:18回答:4

我已经能够使用这个DbSet在Moq实体框架中模拟link

但是,我现在想知道如何模拟对SqlQuery的调用。不确定这是否可行或者如何依赖于模拟的db上下文知道正在调用什么“查询”。

以下是我想要嘲笑的内容。

var myObjects = DbContext.Database
    .SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", "some_value")
    .ToList();

我目前还没有尝试任何东西,因为不知道如何开始嘲笑这个例子。

DbSet的模拟是在下面并重新迭代,我可以正确地模拟返回DbSetMyObject但现在我试图模拟一个返回MyObject列表的SqlQuery。

var dbContext = new Mock<MyDbContext>();
dbContext.Setup(m => m.MyObjects).Returns(mockObjects.Object);

dbContext.Setup(m => m.Database.SqlQuery... something along these lines
c# entity-framework moq dbcontext
4个回答
19
投票

Database.SqlQuery<T>未标记为虚拟,但Set<T>.SqlQuery标记为虚拟。

基于Database.SqlQuery<T>文档

即使返回的对象类型是实体类型,上下文也不会跟踪此查询的结果。使用'SqlQuery(String, Object[])'方法返回由上下文跟踪的实体。

Set<T>.SqlQuery文件

默认情况下,返回的实体由上下文跟踪;这可以通过在返回的DbRawSqlQuery上调用AsNoTracking来更改。

然后Database.SqlQuery<T>(String, Object[])应与Set<T>.SqlQuery(String, Object[]).AsNoTracking()等效(仅当T是EF实体,而不是DTO / VM)。

因此,如果您可以将实现替换为:

var myObjects = DbContext
    .Set<MyObject>()
    .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value")
    .AsNoTracking()
    .ToList();

你可以按照以下方式嘲笑它

var list = new[] 
{ 
    new MyObject { Property = "some_value" },
    new MyObject { Property = "some_value" },
    new MyObject { Property = "another_value" }
};

var setMock = new Mock<DbSet<MyObject>>();
setMock.Setup(m => m.SqlQuery(It.IsAny<string>(), It.IsAny<object[]>()))
    .Returns<string, object[]>((sql, param) => 
    {
        // Filters by property.
        var filteredList = param.Length == 1 
            ? list.Where(x => x.Property == param[0] as string) 
            : list;
        var sqlQueryMock = new Mock<DbSqlQuery<MyObject>>();
        sqlQueryMock.Setup(m => m.AsNoTracking())
            .Returns(sqlQueryMock.Object);
        sqlQueryMock.Setup(m => m.GetEnumerator())
            .Returns(filteredList.GetEnumerator());
        return sqlQueryMock.Object;
    });

var contextMock = new Mock<MyDbContext>();
contextMock.Setup(m => m.Set<MyObject>()).Returns(setMock.Object);

9
投票

Database属性和SqlQuery方法没有标记为virtual所以他们can't be mocked(使用Moq;你可以使用different library可以解释这个,但可能比你想要的更惯性)。

您需要使用某种抽象来解决此问题,例如将数据库的整个查询包装在辅助类中:

public interface IQueryHelper
{
    IList<MyObject> DoYourQuery(string value);
}

public class QueryHelper : IQueryHelper
{
    readonly MyDbContext myDbContext;

    public QueryHelper(MyDbContext myDbContext)
    {
        this.myDbContext = myDbContext;
    }

    public IList<MyObject> DoYourQuery(string value)
    {
        return myDbContext.Database.SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", value).ToList();
    }
}

现在您正在测试的方法变为:

public void YourMethod()
{
    var myObjects = queryHelper.DoYourQuery("some_value");
}

然后你在你正在测试的类的构造函数中注入IQueryHelper并模拟它。

你将失去对DoYourQuery的测试覆盖率,但现在查询是so simple there are obviously no deficiencies


9
投票

您可以在数据库上下文中添加一个虚拟方法,您可以在单元测试中覆盖它:

public partial class MyDatabaseContext : DbContext
{
    /// <summary>
    /// Allows you to override queries that use the Database property
    /// </summary>
    public virtual List<T> SqlQueryVirtual<T>(string query)
    {
        return this.Database.SqlQuery<T>(query).ToList();
    }
}

1
投票

任何人都应该遇到这个。我用几种方法解决了这个问题。另一种解决这个问题的方法。

  1. 我的上下文是通过接口抽象的。我只需要一些方法: public interface IDatabaseContext { DbSet<T> Set<T>() where T : class; DbEntityEntry<T> Entry<T>(T entity) where T : class; int SaveChanges(); Task<int> SaveChangesAsync(); void AddOrUpdateEntity<TEntity>(params TEntity[] entities) where TEntity : class; }
  2. 我的所有数据库访问都是通过异步方法进行的。在尝试模拟它时会引发一系列新问题。幸运的是 - 它已被回答here. 你获得的异常与IDbAsyncEnumerable的缺失模拟有关。使用提供的解决方案 - 我只是将它扩展了一点,以便我有一个帮助器来返回模拟所有预期属性的Mock>对象。 public static Mock<DbSqlQuery<TEntity>> CreateDbSqlQuery<TEntity>(IList<TEntity> data) where TEntity : class, new() { var source = data.AsQueryable(); var mock = new Mock<DbSqlQuery<TEntity>>() {CallBase = true}; mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression); mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType); mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator()); mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider)); mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator())); mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity()); mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; }); mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; }); return mock; }
  3. 最后 - 使用@Yulium Chandra提供的解决方案 - 我使用模拟上下文测试原始SQL如下所示: public Mock<DbSet<TestModel>> MockDbSet { get; } .... MockDbSet.Setup(x => x.SqlQuery(It.IsAny<string>)) .Returns<string,object[]> ((sql, param) => { var sqlQueryMock = MockHelper.CreateDbSqlQuery(Models); sqlQueryMock.Setup(x => x.AsNoTracking()) .Returns(sqlQueryMock.Object); return sqlQueryMock.Object; });
© www.soinside.com 2019 - 2024. All rights reserved.