【问题标题】:How to mock IFindFluent interface如何模拟 IFindFluent 界面
【发布时间】:2017-04-04 09:45:03
【问题描述】:

我正在使用 mongo db 编写单元测试,我需要以某种方式测试适用于从 mongo 返回的数据的方法。例如方法

IFindFluent<Transcript, Transcript> GetTranscriptByUserId(int userId);

应该返回一些成绩单。但相反,它返回具有几个道具的接口 - FilterOptionsSort 和其他。

我要测试Paginator&lt;T&gt;类的方法Paginane

public PaginatedObject<T> Paginate(IFindFluent<T,T> items, int limit, int page)
{
    if (limit == 0)
    {
        limit = 10;
    }

    int count = (int)items.Count();
    var lastPage = (count / limit) + 1;
    if (page <= 0)
    {
        page = 1;
    }

    if (page > lastPage)
    {
        page = lastPage;
    }

    var request = items.Skip((page - 1) * limit).Limit(limit);
    var itemsToReturn = request.ToList();

    var pages = new PaginatedObject<T>
    {
        Entries = itemsToReturn,
        Limit = limit,
        Total = count,
        Page = page
    };

    return pages;
}

第一个参数是接口IFindFluent&lt;T,T&gt; 项目。所以,当我调用CountSkipLimit 时,我应该模拟它以返回项目。但是这些方法很容易被嘲笑。

mockIfindFluent = new Mock<IFindFluent<Transcript, Transcript>>();

mockIfindFluent.Setup(s => s.Limit(It.IsAny<int>())).Returns(mockIfindFluent.Object);
mockIfindFluent.Setup(i => i.Skip(It.IsAny<int>())).Returns(mockIfindFluent.Object);
mockIfindFluent.Setup(i => i.Count(CancellationToken.None)).Returns(3);

我打电话给ToList()时遇到的真正问题。

我遇到异常,我无法模拟不属于模型的属性等等。

【问题讨论】:

  • 看看它是如何被开发者测试的,看看他们是否提供了足够的例子让你实现你想要的github.com/mongodb/mongo-csharp-driver/blob/master/tests/…
  • 显示minimal reproducible example,说明您要测试的内容。这应该有助于提供更好的答案。
  • ToList 是接口上的扩展方法,不能用 moq 模拟。在查看源代码之后,仅仅为了单元测试来模拟它的功能似乎非常复杂。我建议在您控制的抽象背后抽象出所需的功能,以便您更容易模拟和测试。您应该避免模拟/测试您无法控制的代码。
  • 查看这个类似的建议stackoverflow.com/questions/31665990/…
  • 谢谢我也看到了这个答案,并决定避免在 repo 之外使用这个界面。

标签: c# mongodb unit-testing moq mongodb-.net-driver


【解决方案1】:

从这个https://gist.github.com/mizrael/a061331ff5849bf03bf2 和对我有用的扩展实现中获得了一些灵感。我创建了一个 IFindFluent 接口的假实现,它依赖于 IAsyncCursor 接口,所以我也做了一个假实现,并使用它作为我想要设置的模拟方法的回报。在那个假实现中,我初始化了一个可枚举并在我正在使用的方法中返回它。你仍然可以更有创意,玩任何你想返回的东西。到目前为止,这对我有用。

这是一个假的实现。

public class FakeFindFluent<TEntity, TProjection> : IFindFluent<TEntity, TEntity>
{
    private readonly IEnumerable<TEntity> _items;

    public FakeFindFluent(IEnumerable<TEntity> items)
    {
        _items = items ?? Enumerable.Empty<TEntity>();
    }

    public FilterDefinition<TEntity> Filter { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

    public FindOptions<TEntity, TEntity> Options => throw new NotImplementedException();

    public IFindFluent<TEntity, TResult> As<TResult>(MongoDB.Bson.Serialization.IBsonSerializer<TResult> resultSerializer = null)
    {
        throw new NotImplementedException();
    }

    public long Count(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public Task<long> CountAsync(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public long CountDocuments(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public Task<long> CountDocumentsAsync(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public IFindFluent<TEntity, TEntity> Limit(int? limit)
    {
        throw new NotImplementedException();
    }

    public IFindFluent<TEntity, TNewProjection> Project<TNewProjection>(ProjectionDefinition<TEntity, TNewProjection> projection)
    {
        throw new NotImplementedException();
    }

    public IFindFluent<TEntity, TEntity> Skip(int? skip)
    {
        throw new NotImplementedException();
    }

    public IFindFluent<TEntity, TEntity> Sort(SortDefinition<TEntity> sort)
    {
        throw new NotImplementedException();
    }

    public IAsyncCursor<TEntity> ToCursor(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public Task<IAsyncCursor<TEntity>> ToCursorAsync(CancellationToken cancellationToken = default)
    {
        IAsyncCursor<TEntity> cursor = new FakeAsyncCursor<TEntity>(_items);
        var task = Task.FromResult(cursor);

        return task;
    }
}


public class FakeAsyncCursor<TEntity> : IAsyncCursor<TEntity>
{
    private IEnumerable<TEntity> items;

    public FakeAsyncCursor(IEnumerable<TEntity> items)
    {
        this.items = items;
    }

    public IEnumerable<TEntity> Current => items;

    public void Dispose()
    {
        //throw new NotImplementedException();
    }

    public bool MoveNext(CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken = default)
    {
        return Task.FromResult(false);
    }
}

这是我如何设置我的模拟方法以返回我想要的用于单元测试的内容。

mockParticipantRepository
                .Setup(x => x.FindByFilter(It.IsAny<FilterDefinition<Participant>>()))
                .Returns(new FakeFindFluent<Participant, Participant>(participantsByRelation));

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 2019-10-09
    • 2011-09-11
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    相关资源
    最近更新 更多