C# NSubstitute mocking Mongo IFindFluent不起作用

C# NSubstitute mocking Mongo IFindFluent不起作用,c#,mongodb,unit-testing,nsubstitute,C#,Mongodb,Unit Testing,Nsubstitute,我在我的存储库中进行dome单元测试,直到我遇到一个奇怪的错误。四处搜索,看看我是否犯了已知的错误,我可以简化它,并注意到我得到了相同的错误。 看起来我无法正确模拟IFindFluent接口,我想知道我做错了什么。此测试: [Fact] public void Test() { var ff = Substitute.For<IFindFluent<string, string>>(); ff.FirstOrDefa

我在我的存储库中进行dome单元测试,直到我遇到一个奇怪的错误。四处搜索,看看我是否犯了已知的错误,我可以简化它,并注意到我得到了相同的错误。 看起来我无法正确模拟IFindFluent接口,我想知道我做错了什么。此测试:

    [Fact]
    public void Test()
    {
        var ff = Substitute.For<IFindFluent<string, string>>();
        ff.FirstOrDefaultAsync().Returns("asd");
    }
[事实]
公开无效测试()
{
var ff=替换为();
ff.FirstOrDefaultAsync().返回(“asd”);
}
正在返回此错误消息:

NSSubstitute.Exceptions.CouldNotSetReturns由于类型不匹配异常: 无法为IDisposable.Dispose返回Task`1类型的值(应为 键入Void)

确保在调用替换项(对于)后调用了Returns() 示例:mySub.SomeMethod().Returns(value)),而您不是 在Returns()中配置其他替代项(例如,避免 this:mySub.SomeMethod().返回(ConfigOtherSub())

如果替换的是类而不是接口,请检查 对您的替代者的呼叫是针对虚拟/抽象成员的。回来 无法为非虚拟/非抽象成员配置值

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

可能存在问题的使用: mySub.SomeMethod().返回(ConfigOtherSub());相反,请尝试: var returnValue=ConfigOtherSub(); mySub.SomeMethod().Returns(returnValue)

在 nssubstitute.Core.ConfigureCall.CheckResultistCompatibleWithCall(IReturn 返回值,ICallSpecification(规格)位于 NSubstitute.Core.ConfigureCall.SetResultForLastCall(IReturn 值返回,匹配args匹配args)位于 nssubstitute.Core.SubstitutionContext.LastCallShouldReturn(IReturn 值,MatchArgs MatchArgs)位于 CorporateActions.Tests.Persistence.RepositoryTests.Test()


我已经到处搜索过了,但是关于它的大多数常见错误都不适合这个简单的实现。想一想这为什么不起作用吗?

FirstOrDefaultAsync是
IFindFluent
的一种扩展方法。不幸的是,NSU替代了对扩展方法的模拟

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

IFindFluentExtensions.FirstOrDefaultAsync()是:

因此,您需要模拟
iasyncursorsource.tocursorsync()
并检查
iasyncursorextensions.FirstOrDefaultAsync()
扩展方法

iasyncursorextensions.FirstOrDefaultAsync()
是:

这里我们看到,我们还应该模拟
iasyncursor.MoveNextAsync()
iasyncursor.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);
}
[事实]
公共void TestMethod()
{
var cursorMock=Substitute.For();
cursorMock.MoveNextAsync().Returns(Task.FromResult(true)、Task.FromResult(false));
cursorMock.Current.Returns(new[]{“asd”});
var ff=替换为();
返回(Task.FromResult(cursorMock));
ff.限制(1).返回(ff);
var result=ff.FirstOrDefaultAsync().result;
断言。相等(“asd”,结果);
}

FirstOrDefaultAsync
IFindFluent
的扩展方法。不幸的是,NSUBST模拟扩展方法

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

IFindFluentExtensions.FirstOrDefaultAsync()是:

因此,您需要模拟
iasyncursorsource.tocursorsync()
并检查
iasyncursorextensions.FirstOrDefaultAsync()
扩展方法

iasyncursorextensions.FirstOrDefaultAsync()
是:

这里我们看到,我们还应该模拟
iasyncursor.MoveNextAsync()
iasyncursor.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);
}
[事实]
公共void TestMethod()
{
var cursorMock=Substitute.For();
cursorMock.MoveNextAsync().Returns(Task.FromResult(true)、Task.FromResult(false));
cursorMock.Current.Returns(new[]{“asd”});
var ff=替换为();
返回(Task.FromResult(cursorMock));
ff.限制(1).返回(ff);
var result=ff.FirstOrDefaultAsync().result;
断言。相等(“asd”,结果);
}

您可以发布
IFindFluent.FirstOrDefaultAsync的签名吗?或者它是一种扩展方法?正如CodeFuller在下面的回答中所指出的,它是一种扩展方法。我不知道我不能直接模拟它。你能发布
IFindFluent.FirstOrDefaultAsync
的签名吗?或者它是一种扩展方法?正如CodeFuller在下面的回答中所指出的,它是一种扩展方法。我不知道我不能直接嘲笑它。一个很棒的解释和一个真正完整的答案。非常感谢你!这是一个很好的答案,但我认为还值得一提的是这种方法的风险——如果任何扩展实现发生变化,这种测试将非常脆弱(除非这是我们试图测试的?)。也许值得将此函数包装在一个可测试的接口后面,作者可以完全控制并有信心进行模拟,然后在更现实的场景中测试实际实现(即没有模拟)。这是一个很棒的解释和一个真正完整的答案。非常感谢你!这是一个很好的答案,但我认为还值得一提的是这种方法的风险——如果实现任何扩展,这个测试都将非常脆弱
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();
    }
}
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>();
    }
}
[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);
}