首页 > 解决方案 > 使用 IMongoQueryable 进行单元测试。System.InvalidCastException:无法将 EnumerableQuery 类型的对象转换为 IOrderedMongoQueryable 类型

问题描述

我使用 GetAll() 方法跟踪服务,并编写了测试该方法的测试。

public partial class DocumentTypeService : IDocumentTypeService
    {
        private readonly IRepository<DocumentType> _documentTypeRepository;
        private readonly IMediator _mediator;

        public DocumentTypeService(IRepository<DocumentType> documentTypeRepository, IMediator mediator)
        {
            _documentTypeRepository = documentTypeRepository;
            _mediator = mediator;
        }

        public virtual async Task<IList<DocumentType>> GetAll()
        {
            var query = from t in _documentTypeRepository.Table
                        orderby t.DisplayOrder
                        select t;
            return await query.ToListAsync();
        }
       
    }

这是我的测试方法GetAllDocumentTypes():

[TestClass()]
    public class DocumentTypeServiceTests
    {
        private Mock<IRepository<DocumentType>> _documentTypeRepositoryMock;
        private DocumentTypeService _documentTypeService;
        private Mock<IMediator> _mediatorMock;
        private Mock<IMongoQueryable<DocumentType>> _mongoQueryableMock;
        private List<DocumentType> _expected;
        private IQueryable<DocumentType> _expectedQueryable; 

        [TestInitialize()]
        public void Init()
        {
            _mediatorMock = new Mock<IMediator>();
            _documentTypeRepositoryMock = new Mock<IRepository<DocumentType>>();
            _mongoQueryableMock = new Mock<IMongoQueryable<DocumentType>>();
            _expected =  new List<DocumentType>
            {
                new DocumentType() {Name = "name1", Description = "t1", DisplayOrder = 0},
                new DocumentType() {Name = "name2", Description = "t2", DisplayOrder = 1}
            };
            _expectedQueryable = _expected.AsQueryable();
            _mongoQueryableMock.Setup(x => x.ElementType).Returns(_expectedQueryable.ElementType);
            _mongoQueryableMock.Setup(x => x.Expression).Returns(_expectedQueryable.Expression);
            _mongoQueryableMock.Setup(x => x.Provider).Returns(_expectedQueryable.Provider);
            _mongoQueryableMock.Setup(x => x.GetEnumerator()).Returns(_expectedQueryable.GetEnumerator());
                                  
            _documentTypeRepositoryMock.Setup(x => x.Table).Returns(_mongoQueryableMock.Object);
            _documentTypeService = new DocumentTypeService(_documentTypeRepositoryMock.Object, _mediatorMock.Object);
        }

        
        [TestMethod()]
        public async Task GetAllDocumentTypes()
        {
            var actual = await _documentTypeService.GetAll();
            Assert.AreEqual(_expected.Count, actual.Count);
        }
    }

得到错误:

Message: 
    Test method Grand.Services.Tests.Documents.DocumentTypeServiceTests.GetAllDocumentTypes threw exception: 
    System.InvalidCastException: Unable to cast object of type 'System.Linq.EnumerableQuery`1[Grand.Domain.Documents.DocumentType]' to type 'MongoDB.Driver.Linq.IOrderedMongoQueryable`1[Grand.Domain.Documents.DocumentType]'.
  Stack Trace: 
    MongoQueryable.OrderBy[TSource,TKey](IMongoQueryable`1 source, Expression`1 keySelector)
    DocumentTypeService.GetAll() line 38
    DocumentTypeServiceTests.GetAllDocumentTypes() line 101
    ThreadOperations.ExecuteWithAbortSafety(Action action)

您能否解释一下为什么 type 不是 IOrderedMongoQueryable 以及如何解决这个问题?谢谢

标签: c#asp.net-coremoqmongodb-.net-driver

解决方案


新 (2020-09-15)

我尽可能地复制了你的努力。有两个问题。

首先,您的GetAll()方法包括一个orderby,这是强制IMongoQueryable成为IOrderedMongoQueryable。但是,_mongoQueryableMock不返回IOrderedMongoQueryable。如果您尝试将_mongoQueryableMock's IMongoQueryable替换为IOrderedMongoQueryable,那也会失败。我没有找到一种方法来_expectedQueryable允许IOrderedQueryable.

其次,异步可能会给你带来麻烦。如果没有异步,我可以将GetAll()查询更改为这样的内容,这会在应用 orderby 之前解析查询:

   public List<DocumentType> GetAll()
   {
       var query = from t in _documentTypeRepository.Table.ToList()
                   orderby t.DisplayOrder
                   select t;
       return query.ToList();
   }

但是,我没有找到管理 ToListAsync 的方法。

总而言之,我回到了我最初的建议。不要试图模拟 IMongoQueryable。更改服务以接受 MongoClient。无论哪种方式,您都在嘲笑 Mongo,但接受 IMongoClient 您是在直接使用它,而不是将其隐藏在另一个抽象后面。

        private readonly IMongoClient _mongoClient;
        public DocumentTypeService(IMongoClient mongoClient) {...}

原创 (2020-09-14)

我不能完全确定,但我相信这是因为_documentTypeRepository没有返回IMongoQueryable。它返回 a Table,这将强制一个有序的 mongo 可查询。

为此,使用我在这里描述的模拟方法工作,_documentTypeRepository需要返回 IMongoQueryable然后转换为Table,或者查看 IQueryable 是否可以转换为IOrderedMongoQueryable

简而言之,没有什么可以嘲笑IOrderedMongoQueryable.

老实说,这一切都表明存储库可能与实现的耦合过于紧密。强制IMongoQueryableIQueryable代码异味和反模式。您可以考虑通过以下两种方式之一重新考虑您的服务和/或存储库层:

  1. 创建一个_documentTypeRepository.GetAll()将 MongoDb 结果映射到DocumentType. 或者
  2. 不要在服务中使用存储库模式。相反,注入 IMongoClient,从 Mongo 返回数据作为GetAll方法的一部分,并映射到那里的 DocumentType。

推荐阅读