NSubstitute 嘲笑 Mongo IFindFluent 不起作用

str*_*ind 1 c# unit-testing mongodb nsubstitute

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

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

正在返回此错误消息:

NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException:无法为 IDisposable.Dispose 返回 Task`1 类型的值(预期类型为 Void)。

确保在调用替代品后调用了 Returns()(例如:mySub.SomeMethod().Returns(value)),并且您没有在 Returns() 中配置其他替代品(例如,避免这样:mySub.SomeMethod( ).返回(ConfigOtherSub()))。

如果您替换了一个类而不是一个接口,请检查对您的替代品的调用是否在虚拟/抽象成员上。不能为非虚拟/非抽象成员配置返回值。

正确使用: mySub.SomeMethod().Returns(returnValue);

可能有问题的使用:mySub.SomeMethod().Returns(ConfigOtherSub()); 而是尝试: var returnValue = ConfigOtherSub(); mySub.SomeMethod().Returns(returnValue);

在 NSubstitute.Core.ConfigureCall.CheckResultIsCompatibleWithCall(IReturn valueToReturn, ICallSpecification spec) 在 NSubstitute.Core.ConfigureCall.SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs) 在 NSubstitute.Core.SubstitutionContext.LastCallShouldReturn(IReturn value, Match.Args) at CorporateArgs match.Action Persistence.RepositoryTests.Test()

我四处搜索,但最常见的错误并不适合这个简单的实现。任何想法为什么这不起作用?

Cod*_*ler 5

FirstOrDefaultAsync是 的扩展方法IFindFluent<TDocument, TProjection>。不幸的是,NSubstitute不支持模拟扩展方法。

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

IFindFluentExtensions.FirstOrDefaultAsync()定义为

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

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

IAsyncCursorSourceExtensions.FirstOrDefaultAsync定义为

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

所以你需要模拟IAsyncCursorSource<TDocument>.ToCursorAsync()和检查IAsyncCursorExtensions.FirstOrDefaultAsync()扩展方法。

IAsyncCursorExtensions.FirstOrDefaultAsync()定义为

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

所以最后一个分析方法是IAsyncCursorExtensions.GetFirstBatchAsync(),它被定义为

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

在这里我们看到我们也应该模拟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);
}
Run Code Online (Sandbox Code Playgroud)