NSubstitute 模拟 Mongo IFindFluent 不起作用

NSubstitute mocking Mongo IFindFluent does not work

我在我的存储库周围做圆顶单元测试,直到我得到一个奇怪的错误。四处搜索看看我是否没有犯已知错误,我可以简化它并注意到我遇到了同样的错误。 看起来我无法正确模拟 IFindFluent 接口,我想知道我做错了什么。本次测试:

    [Fact]
    public void Test()
    {
        var ff = Substitute.For<IFindFluent<string, string>>();
        ff.FirstOrDefaultAsync().Returns("asd");
    }

返回此错误消息:

NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException : Can not return value of type Task`1 for IDisposable.Dispose (expected type Void).

Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)), and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).

If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member. Return values cannot be configured for non-virtual/non-abstract members.

Correct use: mySub.SomeMethod().Returns(returnValue);

Potentially problematic use: mySub.SomeMethod().Returns(ConfigOtherSub()); Instead try: var returnValue = ConfigOtherSub(); mySub.SomeMethod().Returns(returnValue);

at NSubstitute.Core.ConfigureCall.CheckResultIsCompatibleWithCall(IReturn valueToReturn, ICallSpecification spec) at NSubstitute.Core.ConfigureCall.SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs) at NSubstitute.Core.SubstitutionContext.LastCallShouldReturn(IReturn value, MatchArgs matchArgs) at CorporateActions.Tests.Persistence.RepositoryTests.Test()

我四处搜索了一下,但最常见的错误与这个简单的实现不相符。有什么想法为什么这不起作用?

FirstOrDefaultAsyncIFindFluent<TDocument, TProjection>的扩展方法。不幸的是,NSubstitute 模拟了扩展方法。

然而,这并不意味着您不能在这种情况下开发适当的 UT。接口的扩展方法最终会调用这些接口的某些方法。所以这个问题的常见解决方法是检查实际调用了哪些接口方法并模拟它们,而不是模拟整个扩展方法。

IFindFluentExtensions.FirstOrDefaultAsync()defined as:

public static Task<TProjection> FirstOrDefaultAsync<TDocument, TProjection>(this IFindFluent<TDocument, TProjection> find, CancellationToken cancellationToken = default(CancellationToken))
{
    Ensure.IsNotNull(find, nameof(find));

    return IAsyncCursorSourceExtensions.FirstOrDefaultAsync(find.Limit(1), cancellationToken);
}

现在您知道应该模拟 IFindFluent<TDocument, TProjection>.Limit(int? limit) 方法并深入研究 IAsyncCursorSourceExtensions.FirstOrDefaultAsync() 扩展方法。

IAsyncCursorSourceExtensions.FirstOrDefaultAsyncdefined as:

public static async Task<TDocument> FirstOrDefaultAsync<TDocument>(this IAsyncCursorSource<TDocument> source, CancellationToken cancellationToken = default(CancellationToken))
{
    using (var cursor = await source.ToCursorAsync(cancellationToken).ConfigureAwait(false))
    {
        return await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
    }
}

因此您需要模拟 IAsyncCursorSource<TDocument>.ToCursorAsync() 并检查 IAsyncCursorExtensions.FirstOrDefaultAsync() 扩展方法。

IAsyncCursorExtensions.FirstOrDefaultAsync()defined as:

public static async Task<TDocument> FirstOrDefaultAsync<TDocument>(this IAsyncCursor<TDocument> cursor, CancellationToken cancellationToken = default(CancellationToken))
{
    using (cursor)
    {
        var batch = await GetFirstBatchAsync(cursor, cancellationToken).ConfigureAwait(false);
        return batch.FirstOrDefault();
    }
}

所以最后要分析的方法是IAsyncCursorExtensions.GetFirstBatchAsync(),也就是defined as:

private static async Task<IEnumerable<TDocument>> GetFirstBatchAsync<TDocument>(IAsyncCursor<TDocument> cursor, CancellationToken cancellationToken)
{
    if (await cursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
    {
        return cursor.Current;
    }
    else
    {
        return Enumerable.Empty<TDocument>();
    }
}

这里看到我们还应该mock IAsyncCursor.MoveNextAsync()IAsyncCursor.Current.

这是模拟所有已发现调用的测试方法:

[Fact]
public void TestMethod()
{
    var cursorMock = Substitute.For<IAsyncCursor<string>>();
    cursorMock.MoveNextAsync().Returns(Task.FromResult(true), Task.FromResult(false));
    cursorMock.Current.Returns(new[] { "asd" });

    var ff = Substitute.For<IFindFluent<string, string>>();
    ff.ToCursorAsync().Returns(Task.FromResult(cursorMock));
    ff.Limit(1).Returns(ff);

    var result = ff.FirstOrDefaultAsync().Result;
    Assert.AreEqual("asd", result);
}