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

Dav*_*vid 19 c# entity-framework moq dbcontext

我已经能够DbSet使用此链接模拟来自Moq的实体框架.

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

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

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

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

的嘲讽DbSet是下方再次重申,我可以正确模拟返回DbSetMyObject的,但现在我试图嘲弄返回列表SQLQuery对MyObject的.

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

dbContext.Setup(m => m.Database.SqlQuery... something along these lines
Run Code Online (Sandbox Code Playgroud)

Yul*_*dra 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()(只有TEF实体,而不是DTO/VM).

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

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

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

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);
Run Code Online (Sandbox Code Playgroud)


小智 10

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Pat*_*irk 9

Database财产和SqlQuery没有标记的方法virtual,使他们不能被嘲笑(使用起订量,你可以使用一个不同的库,可以考虑到这一点,但可能更惯性比你想).

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public void YourMethod()
{
    var myObjects = queryHelper.DoYourQuery("some_value");
}
Run Code Online (Sandbox Code Playgroud)

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

您将缺少测试覆盖率DoYourQuery,但现在查询非常简单,显然没有任何缺陷.