The Problem

The latest MongoDb driver for .Net doesn't have a way to convert a collection such as List to IMongoQueryable. If the code depends on that interface, it needs to be mocked, but how to set the concrete data?

The Solution

What you'll need to run this sample yourself.

Let's say you've settled on using MongoDb as your NoSQL data store. You write a simple repository pattern with one method to query for any concrete type.1

	public interface IMongoRepository<T> where T : class
	{
		public IMongoQueryable<T> QueryAll();
	}

You also have a simple Customer service that calls the repository

public class CustomerService
{
	IMongoRepository<Customer> _customerRepository = null;
	public CustomerService(IMongoRepository<Customer> customerRepository)
	{
		_customerRepository = customerRepository;
	}

	public List<Customer> GetCustomers()
	{
		return _customerRepository.QueryAll().ToList();
	}
}

Finally, you start writing the following test. But you discover there's no way to get a concrete instance of IMongQueryable. There used to be, but it's legacy code.

public class CustomerService_Should
{
	[Fact]
	public void Return_customers()
	{
		var expected = new List<Customer>() { new Customer() { Id = 1 } };
		var customerRepository = Substitute.For<IMongoRepository<Customer>>();
		
		//return the mocked data. But how to convert the list into IMongoQueryable???

		customerRepository.QueryAll().Returns([argh, what goes here??]);
		var service = new CustomerService(customerRepository);
		var actual = service.GetCustomers();
		Assert.Equal(expected.Count, actual.Count);
		Assert.Equal(expected.First().Id, actual.First().Id);
	}
}

Like me, you probably try all kinds of typecasting before realizing you're always trying to do something impossible. Finally, you find the answer on Stack Overflow. There are two ways to mock up the data, and both make the IMongoQueryable class accept IQueryable.

Using NSubstitute

public class CustomerService_Should
{
	[Fact]
	public void Return_customers()
	{
		var expected = new List<Customer>() { new Customer() { Id = 1 } };
		var customerRepository = Substitute.For<IMongoRepository<Customer>>();
		
		//Mock IMongoQueryable to accept IQueryable, enabling just enough of the interface
		//to work
		var expectedQueryable = expected.AsQueryable();
		var mockQueryable = Substitute.For<IMongoQueryable<Customer>>();
		mockQueryable.ElementType.Returns(expectedQueryable.ElementType);
		mockQueryable.Expression.Returns(expectedQueryable.Expression);
		mockQueryable.Provider.Returns(expectedQueryable.Provider);
		mockQueryable.GetEnumerator().Returns(expectedQueryable.GetEnumerator());

		//return the mocked data
		customerRepository.QueryAll().Returns(mockQueryable);
		var service = new CustomerService(customerRepository);
		var actual = service.GetCustomers();
		Assert.Equal(expected.Count, actual.Count);
		Assert.Equal(expected.First().Id, actual.First().Id);
	}
}

Pretty slick.

Creating MongoQueryable

A simple concrete class that allows setting a List property.

public class MongoQueryable<T> : IMongoQueryable<T>
{
	public List<T> MockData { get; set; }

	public Type ElementType => MockData.AsQueryable().ElementType;

	public Expression Expression => MockData.AsQueryable().Expression;

	public IQueryProvider Provider => MockData.AsQueryable().Provider;

	public IEnumerator<T> GetEnumerator() => MockData.AsQueryable().GetEnumerator();
	IEnumerator IEnumerable.GetEnumerator() => MockData.AsQueryable().GetEnumerator();

	public QueryableExecutionModel GetExecutionModel() => throw new NotImplementedException();

	public IAsyncCursor<T> ToCursor(CancellationToken cancellationToken = default) => throw new NotImplementedException();

	public Task<IAsyncCursor<T>> ToCursorAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();

}

The test.

[Fact]
public void Return_customers2()
{
	var expected = new List<Customer>() { new Customer() { Id = 1 } };
	var customerRepository = Substitute.For<IMongoRepository<Customer>>();

	//Mock IMongoQueryable using a class
	var mockQueryable = new MongoQueryable<Customer>();
	mockQueryable.MockData = expected;

	//return the mocked data
	customerRepository.QueryAll().Returns(mockQueryable);
	var service = new CustomerService(customerRepository);
	var actual = service.GetCustomers();
	Assert.Equal(expected.Count, actual.Count);
	Assert.Equal(expected.First().Id, actual.First().Id);
}

References


  1. The comments in Stack Overflow point out that using IMongoQueryable--or IQueryable-- isn't ideal because it tightly couples the code to MongoDb or to a Queryable backend. It might be better to use a truly generic repository and convert to/from MongoDb (or other database) as needed.