Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to (should I) mock DocumentClient for DocumentDb unit testing?

Tags:

From the new CosmosDb emulator I got sort of a repository to perform basic documentdb operations, this repository gets injected to other classes. I wanted to unit test a basic query.

public class DocumentDBRepository<T> where T : class {  //Details ommited...      public IQueryable<T> GetQueryable()     {         return _client.CreateDocumentQuery<T>(             UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId),             new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true });     }      public async Task<IEnumerable<T>> ExecuteQueryAsync(IQueryable<T> query)     {         IDocumentQuery<T> documentQuery = query.AsDocumentQuery();         List<T> results = new List<T>();         while (documentQuery.HasMoreResults)         {             results.AddRange(await documentQuery.ExecuteNextAsync<T>());         }          return results;     }   } 

This repository needs a document client to work, which also gets injected on the constructor.

public DocumentDBRepository(string databaseId, IDocumentClient client) {     _client = client;     _databaseId = databaseId;     _collectionId = GetCollectionName(); } 

My initial approach was to use the CosmosDb emulator, but that required the emulator to run which I don't like and makes the tests slower.

My second approach was to try and use a mock of the document client.

var data = new List<MyDocumentClass> {     new MyDocumentClass{ Description= "BBB" },     new MyDocumentClass{ Description= "ZZZ" },  } .AsQueryable() .OrderBy(q => q.Description); var client = new Mock<IDocumentClient>(); client.As<IDocumentClient>()     .Setup(foo => foo.CreateDocumentQuery<MyDocumentClass>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))     .Returns(data);  DocumentDBRepository<MyDocumentClass> repo= new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object); 

The code that uses this repository works like this:

var query = _documentsRepository.GetQueryable()                 .Where(d => d.Description = description)                 .OrderByDescending(d => d.description)                 .Take(100); //Execute query async fails.  var result = await _documentsRepository.ExecuteQueryAsync(query); 

It fails because the repository tries to convert the IQueryable to a IDocumentQuery object, which is very specific to DocumentDb api (See method ExecuteQueryAsync above). Later on, it executes HasMoreResults method on it. So the problem is, even if I mock .asDocumentQuery() to return my object, I don't know how to provide a result for HasMoreResults and ExecuteNextAsync so that it makes sense for my unit tests.

My third option would be to straight mock my repository instead of the DocumentClient object. Would be, I think, simpler, but I wouldn't be testing much of the DocumentDb api.

like image 970
Ernesto Avatar asked Jan 19 '18 21:01

Ernesto


People also ask

Should I create mock database abstractions to write unit tests?

I’m here to assert that creating mock database abstractions to write unit tests is almost always a bad idea. I work on a lot of RESTful Go API servers, and I do my best to write small testable functions so I don’t have to write useless mocking logic.

How to mock a DataContext in a test?

There are ways to mock it but you can simply go around that and create a fake implementation of a Repository and UnitOfWork to use in your tests. If you go the extra mile and create a viable mock for a DataContext then you shouldn't create mocks for your Repository and UnitOfWork.

Why mock database code is unused in production?

The mock database code is unused in production. We are testing code that literally doesn’t matter. We technically have better “test coverage” with this approach, but our tests aren’t actually any more robust. We get a false sense of security. We have made the real code harder to find by abstracting it behind an interface.

Do I need a database for unit testing?

Only the Repository and UnitOfWork classes themselves (the ones that contain the actual database connection) would need a real database to be unit-tested and if their implementation is simple enough, you can even choose to defer testing of those classes till the integration or end-to-end tests.


2 Answers

The key to this is that the CreateDocumentQuery you are calling, though shown as returning IOrderedQueryable, the encapsulated result will also be derived from IDocumentQuery which is what would allow .AsDocumentQuery() to work.

Now normally you should not be mocking what you do not own. However in this case if you want to exercise ExecuteQueryAsync to completion you can create a fake abstraction that will allow the test to be exercised to completion.

The following Example shows how it can be done.

[TestClass] public class DocumentDBRepositoryShould {     /// <summary>     /// Fake IOrderedQueryable IDocumentQuery for mocking purposes     /// </summary>             public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T> {      }      [TestMethod]     public async Task ExecuteQueryAsync() {         //Arrange         var description = "BBB";         var expected = new List<MyDocumentClass> {             new MyDocumentClass{ Description = description },             new MyDocumentClass{ Description = "ZZZ" },             new MyDocumentClass{ Description = "AAA" },             new MyDocumentClass{ Description = "CCC" },          };         var response = new FeedResponse<MyDocumentClass>(expected);          var mockDocumentQuery = new Mock<IFakeDocumentQuery<MyDocumentClass>>();         mockDocumentQuery             .SetupSequence(_ => _.HasMoreResults)             .Returns(true)             .Returns(false);          mockDocumentQuery             .Setup(_ => _.ExecuteNextAsync<MyDocumentClass>(It.IsAny<CancellationToken>()))             .ReturnsAsync(response);          var client = new Mock<IDocumentClient>();          client             .Setup(_ => _.CreateDocumentQuery<MyDocumentClass>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))             .Returns(mockDocumentQuery.Object);          var cosmosDatabase = string.Empty;          var documentsRepository = new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object);          //Act         var query = documentsRepository.GetQueryable(); //Simple query.          var actual = await documentsRepository.ExecuteQueryAsync(query);          //Assert         actual.Should().BeEquivalentTo(expected);     } } 
like image 146
Nkosi Avatar answered Sep 18 '22 08:09

Nkosi


Here is Nkosi's answer ported to NSubstitute:

[TestClass] public class DocumentDBRepositoryShould {     [TestMethod]     public async Task ExecuteQueryAsync()     {         // Arrange         var description = "BBB";         var expected = new List<MyDocumentClass> {             new MyDocumentClass{ Description = description },             new MyDocumentClass{ Description = "ZZZ" },             new MyDocumentClass{ Description = "AAA" },             new MyDocumentClass{ Description = "CCC" },          };         var response = new FeedResponse<MyDocumentClass>(expected);          var mockDocumentQuery = Substitute.For<IFakeDocumentQuery<MyDocumentClass>>();          mockDocumentQuery.HasMoreResults.Returns(true, false);         mockDocumentQuery.ExecuteNextAsync<MyDocumentClass>(Arg.Any<CancellationToken>())             .Returns(Task.FromResult(response));                  var client = Substitute.For<IDocumentClient>();         client.CreateDocumentQuery<MyDocumentClass>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())             .ReturnsForAnyArgs(mockDocumentQuery);         var cosmosDatabase = string.Empty;         var documentsRepository = new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client);                  //Act         var actual = await documentsRepository.GetDataAsync(); //Simple query.          //Assert         actual.Should().BeEquivalentTo(expected);     }      public class MyDocumentClass     {         public string Description { get; set; }     }          public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T> {      }          public class DocumentDBRepository<T>     {         private readonly string cosmosDatabase;         private readonly IDocumentClient documentClient;          public DocumentDBRepository(string cosmosDatabase, IDocumentClient documentClient)         {             this.cosmosDatabase = cosmosDatabase;             this.documentClient = documentClient;         }          public async Task<IEnumerable<MyDocumentClass>> GetDataAsync()         {             var documentUri = UriFactory.CreateDocumentCollectionUri(cosmosDatabase, "test-collection");                      var query = documentClient                 .CreateDocumentQuery<MyDocumentClass>(documentUri)                 .AsDocumentQuery();                      var list = new List<MyDocumentClass>();             while (query.HasMoreResults)             {                 var rules = await query.ExecuteNextAsync<MyDocumentClass>();                 list.AddRange(rules);             }             return list;         }     } } 
like image 21
Tjaart Avatar answered Sep 20 '22 08:09

Tjaart