问题
I am using .NET Core 2.0 and the .NET Core MongoDB driver.
I have created a repository like so:
public interface IRepository<T>
{
IMongoQueryable<T> Get()
}
I have done this to give flexibility to whoever uses this to be able to do LINQ much like they would do using EF. The problem is when it comes to unit testing and I'm trying to create an in-memory database so I can check states before and after operation.
Some stuff I tried:
public class InMemoryRepository : IRepository<ConcreteType>
{
private HashSet<ConcreteType> _data = new HashSet<ConcreteType>();
public IMongoQueryable<ConcreteType> Get()
{
return (IMongoQueryable<ConcreteType>)_data.AsQueryable();
}
}
The case doesn't work as the interface for IMongoQueryable
is:
public interface IMongoQueryable<T> : IMongoQueryable, IQueryable, IEnumerable, IQueryable<T>, IEnumerable<T>, IAsyncCursorSource<T>
Another go:
public class InMemoryRepository : IRepository<ConcreteType>
{
private HashSet<ConcreteType> _data = new HashSet<ConcreteType>();
public InMemoryRepository()
{
_mongoQueryableMock = new Mock<IMongoQueryable<ConcreteType>>();
_mongoQueryableMock.Setup(m => m.AsQueryable()).Returns(_data.AsQueryable);
}
public IMongoQueryable<ConcreteType> Get()
{
return _mongoQueryableMock.Object;
}
}
This doesn't work as IMongoQueryable.AsQueryable()
is an extension method and I can't mock/setup that.
回答1:
Configure the mock to be able to handle IQueryable
calls.
public class InMemoryRepository : IRepository<ConcreteType> {
private HashSet<ConcreteType> _data = new HashSet<ConcreteType>();
private Mock<IMongoQueryable<ConcreteType>> _mongoQueryableMock;
public ReviseMeasureRepository() {
var queryableList = _data.AsQueryable();
_mongoQueryableMock = new Mock<IMongoQueryable<ConcreteType>>();
_mongoQueryableMock.As<IQueryable<ConcreteType>>().Setup(x => x.Provider).Returns(queryableList.Provider);
_mongoQueryableMock.As<IQueryable<ConcreteType>>().Setup(x => x.Expression).Returns(queryableList.Expression);
_mongoQueryableMock.As<IQueryable<ConcreteType>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
_mongoQueryableMock.As<IQueryable<ConcreteType>>().Setup(x => x.GetEnumerator()).Returns(() => queryableList.GetEnumerator());
}
public IMongoQueryable<ConcreteType> Get() {
return _mongoQueryableMock.Object;
}
//...
}
With that out of the way I think the design of the repository is leaky and directly couples your code to external dependencies. Consider reviewing the design of the repository abstraction.
回答2:
I was struggling with a similar task and landed on this question. I was not able to mock IMongoQueryable
interface. But, what I came to realize is you don't really have to mock it. IMongoQueryable<T>
implements IQueryable<T>
. As long as you want to test filtering logic, you can use IQueryable<T>
interface instead.
In my case, I created a QueryBuilder
class that accepts IQueryable<T>
as a c-tor argument. Then, It exposes several methods that build the actual filter
{
public class MyCollectionQueryBuilder
{
private IQueryable<MyCollectionItem> query;
public MyCollectionQueryBuilder(IQueryable<MyCollectionItem> query)
{
this.query = query;
}
public MyCollectionQueryBuilder WithCol1Filter(string filter)
{
query = query.Where(a => (a.Col1 == filter));
return this;
}
public IQueryable<CoreFolderResearchModelLink> Build()
{
return query;
}
}
}
Then, in the real code it'll be called like that
{
var queryBuilder = new MyCollectionQueryBuilder(myMongoCollection.AsQueryable()); //IMongoQueryable
q = queryBuilder.WithCol1Filter("filter_value").Build();
var res = q.ToList();
}
And Unit Tests will call it like below
{
private IEnumerable<MyCollectionItem> inputData = new List<MyCollectionItem> {};
var queryBuilder = new MyCollectionQueryBuilder(inputData.AsQueryable()); //Linq IQueryable
q = queryBuilder.WithCol1Filter("filter_value").Build();
var res = q.ToList();
}
At runtime both are resolved to correct interface. So, you can assert the results without connecting to the database
来源:https://stackoverflow.com/questions/46035390/unit-testing-with-imongoqueryable