关于 c#:How to mock Nhibernate .ToListAsync() in Unit-test with moq?

How to mock Nhibernate .ToListAsync() in Unit-test with moq?

我正在尝试使用 moq 在 ASP.NET Core MVC 应用程序中创建单元测试。不幸的是,Linq IQueryable 数据集不支持 Nhibernate.ToListAsync() 并抛出 System.NotSupportedException: 'Source Provider must be a INhQueryProvider'
在这段代码中,我模拟了 INhQueryProvider,但这还不够:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var entities = new List<RequestRole>
{
    new RequestRole()
    {
       Id = 0,
       RequestOperator = new RequestOperator() { Id = 1 }
    },
    new RequestRole()
    {
       Id = 1,
       RequestOperator = new RequestOperator() { Id = 2 }
     }
}
.AsQueryable();

// for ToListAsync Mock INhQueryProvider and set it into IQueryable
var queryableProviderMock = new Mock<INhQueryProvider>();
queryableProviderMock.Setup(x => x.ExecuteAsync<IEnumerable<RequestRole>>(It.IsAny<Expression>(), It.IsAny<CancellationToken>()))
                                 .ReturnsAsync(entities);

var queryableMock = new Mock<IQueryable<RequestRole>>();
queryableMock.Setup(x => x.Provider).Returns(queryableProviderMock.Object);
queryableMock.Setup(x => x.Expression).Returns(entities.Expression);
queryableMock.Setup(x => x.GetEnumerator()).Returns(entities.GetEnumerator());
queryableMock.Setup(x => x.ElementType).Returns(entities.ElementType);

// mock CreateQuery, without this Linq.Where throwing"System.NotSupportedException: 'Source Provider must be a INhQueryProvider'"
queryableProviderMock.As<INhQueryProvider>()
    .Setup(x => x.CreateQuery<RequestRole>(It.IsAny<Expression>()))
    .Returns(queryableMock.Object);

var session = new Mock<ISession>();
session.Setup(s => s.Query<RequestRole>()).Returns(queryableMock.Object);
var returns = session.Object.Query<RequestRole>();

// check work
var tolistasync = await returns
    .Where(x => x.Id != 0)
    .ToListAsync();

在这种情况下 Linq.Where 条件不起作用,因为我设置了相同的对象而不是过滤。
似乎我应该正确地模拟 INhQueryProvider.CreateQuery,但是如何模拟呢?


您需要指示 CreateQuery 使用该表达式。正如您所见,仅返回模拟的可查询对象不会做任何事情。此外,CreateQuery 将需要返回一个带有实现 INhQueryProvider 的提供程序的 IQueryable。问题在于 Provider 属性没有设置器,因此您无法在现有的可查询对象上设置它。

我解决类似问题的方法是创建自己的序列,我可以在其中设置提供程序。

从创建实现IQueryable<T>INhQueryProvider的类开始;为简洁起见,我只实现通过 OP 用例所需的内容。请注意, CreateQuery<T> 返回一个可使用实现 INhQueryProvider:

的提供程序的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class TestingQueryable< T > : IQueryable< T >
{
    private readonly IQueryable< T > _queryable;

    public TestingQueryable(IQueryable< T > queryable)
    {
        _queryable = queryable;    
        Provider = new TestingQueryProvider< T >(_queryable);
    }

    public Type ElementType => _queryable.ElementType;

    public Expression Expression =>  _queryable.Expression;

    public IQueryProvider Provider { get; }

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

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

public class TestingQueryProvider< T > : INhQueryProvider
{
    public TestingQueryProvider(IQueryable< T > source)
    {
        Source = source;
    }

    public IQueryable< T > Source { get; set; }

    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestingQueryable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return Source.Provider.Execute<TResult>(expression);
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }

    public int ExecuteDml<T1>(QueryMode queryMode, Expression expression)
    {
        throw new NotImplementedException();
    }

    public Task<int> ExecuteDmlAsync<T1>(QueryMode queryMode, Expression expression, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public IFutureEnumerable<TResult> ExecuteFuture<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IFutureValue<TResult> ExecuteFutureValue<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
    {
        throw new NotImplementedException();
    }
}

更新您的查询提供程序设置以使用您的 IQueryable 实现:

1
2
3
4
5
6
queryProviderMock
        .Setup(x => x.CreateQuery<RequestRole>(It.IsAny<Expression>()))
        .Returns((Expression providedExpression) =>
        {          
            return new TestingQueryable<RequestRole>(queryable.Provider.CreateQuery<RequestRole>(providedExpression));
        });

运行 .Where(x => x.Id != 0).ToListAsync() 并得到预期的结果:

LINQPad result

工作示例

您可以更进一步,只需设置 ISession 模拟以使用您的 IQueryable 实现,如果您不需要专门模拟它,请取消模拟查询提供程序。如果您知道我的意思,我通常不会模拟模拟返回的内容,因此这符合我的同行评审标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Test]
public async Task Test2()
{
    var requestRoles = new List<RequestRole>();
    requestRoles.Add(new RequestRole { Id = 0, RequestOperator = new RequestOperator { Id = 1 } });
    requestRoles.Add(new RequestRole { Id = 1, RequestOperator = new RequestOperator { Id = 2 } });

    var sessionMock = new Mock<ISession>();
    sessionMock.Setup(s => s.Query<RequestRole>()).Returns(new TestingQueryable<RequestRole>(requestRoles.AsQueryable()));
    var query = sessionMock.Object.Query<RequestRole>();

    var result = await query.Where(x => x.Id != 0).ToListAsync();

    Assert.Multiple(() =>
    {
        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result.Single(), Is.EqualTo(requestRoles.Last()));
    });
}