【问题标题】:NSubstitute mocking Mongo IFindFluent does not workNSubstitute 模拟 Mongo IFindFluent 不起作用
【发布时间】:2018-04-11 18:23:41
【问题描述】:

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

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

正在返回此错误消息:

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

确保在调用替代者后调用 Returns()(对于 例如:mySub.SomeMethod().Returns(value)),而你不是 在 Returns() 中配置其他替代品(例如,避免 这个:mySub.SomeMethod().Returns(ConfigOtherSub()))。

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

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

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

在 NSubstitute.Core.ConfigureCall.CheckResultIsCompatibleWithCall(IReturn valueToReturn,ICallSpecification 规范)在 NSubstitute.Core.ConfigureCall.SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs) 在 NSubstitute.Core.SubstitutionContext.LastCallShouldReturn(IReturn 值,MatchArgs matchArgs)在 CorporateActions.Tests.Persistence.RepositoryTests.Test()

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

【问题讨论】:

  • 能把IFindFluent.FirstOrDefaultAsync的签名发上来吗?还是扩展方法?
  • 正如 CodeFuller 在下面的答案中指出的那样,它是一种扩展方法。不知道我不能直接模拟它。

标签: c# mongodb unit-testing nsubstitute


【解决方案1】:

FirstOrDefaultAsyncIFindFluent&lt;TDocument, TProjection&gt; 的扩展方法。不幸的是,NSubstitute does not support 模拟了扩展方法。

但这并不意味着在这种情况下您不能开发适当的 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&lt;TDocument, TProjection&gt;.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&lt;TDocument&gt;.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>();
    }
}

在这里我们看到我们还应该模拟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);
}

【讨论】:

  • 一个很棒的解释和一个非常完整的答案。非常感谢!
  • 这是一个很好的答案,但我认为这种方法的风险也值得一提——如果任何扩展实现发生变化,这个测试将非常脆弱(除非这是我们的试图测试?)。可能值得将此函数包装在作者可以完全控制并且可以自信地模拟的可测试接口后面,然后在更真实的场景中测试实际实现(即没有模拟)。
猜你喜欢
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多