0

I am getting the following exception when trying to unit test a db call to a mongo database that uses ProjectionDefinition: "Value cannot be null.\r\nParameter name: projection". This is caused because during assignment for the MongoDb C# driver ProjectionDefinition class, there is a null check that throws this exception. Here is the call stack for the exception:

{System.ArgumentNullException: Value cannot be null.
Parameter name: projection
   at MongoDB.Driver.Core.Misc.Ensure.IsNotNull[T](T value, String paramName)
   at MongoDB.Driver.KnownResultTypeProjectionDefinitionAdapter`2..ctor(ProjectionDefinition`1 projection, IBsonSerializer`1 projectionSerializer)
   at MongoDB.Driver.ProjectionDefinition`2.op_Implicit(ProjectionDefinition`1 projection)
   at DataCollection.Data.Mongo.Test.Collection.ComponentRecordCollectionTest.LogsExceptionWhenOnGetRecordUserViews() in C:\Repositories\Folio\folio-data-collection-api\src\DataCollection.Data.Mongo.Test\Collection\ComponentRecordCollectionTest.cs:line 447}

I am using the NSubstitute Arg.Is to test the value of the projection within my function.

var expectedProjection = Arg.Is<ProjectionDefinition<ComponentRecordDataModel>>(
                x => x.ToJson().Equals(projection.ToJson()));

I've noticed that this causes expectedProjection to be null which triggers the exception. I'm not sure how this works but I am doing the exact same thing with testing FilterDefinition and it works with no issues. I've even tested with the examples from the NSubstitue documentation an even though the Arg.Is returns null the argument matchers work.

Here is the function that I am trying to test:

public ComponentRecordDataModel GetRecordUserViews(string id)
{
     var filter = Builders<ComponentRecordDataModel>.Filter.Eq(x => x.Id, id);
     var projection = Builders<ComponentRecordDataModel>.Projection.Include(x => x.UserViews);
     var result = MongoContext.Find(filter, projection).FirstOrDefault();
     return result;
}

and here is the test:

[Fact]
public void LogsExceptionWhenOnGetRecordUserViews()
{
    var filter = Builders<ComponentRecordDataModel>.Filter.Eq(x => x.Id, _recordModel.Id);
    var projection = Builders<ComponentRecordDataModel>.Projection.Include(x => x.UserViews);

    var expectedFilter = Arg.Is<FilterDefinition<ComponentRecordDataModel>>(
                x => x.ToJson().Equals(filter.ToJson()));
    var expectedProjection = Arg.Is<ProjectionDefinition<ComponentRecordDataModel>>(
                x => x.ToJson().Equals(projection.ToJson()));

    _mongoContext.Find(expectedFilter, expectedProjection).Throws(_exception);

    Assert.Throws(typeof(DataAccessException), () => _collection.GetRecordUserViews(_recordModel.Id));
    _logger.CheckReceivedLog(LogLevel.Error, LoggingEvents.GeneralException, _exception.Message);
 }

I'm wondering if there is something I am doing wrong here or if someone has an alternative approach to getting this code tested. Thanks in advance!

Nick Dichiaro
  • 330
  • 1
  • 2
  • 10
  • I think this may be [misusing `Arg.Is`](http://nsubstitute.github.io/help/argument-matchers/#how_not_to_use_argument_matchers); these should only be used in conjunction with `Received()` or `Returns()` on a substitute. I can't see where are substitute (created with `Substitute.For()` or similar) is being used in your tests? In which case `Arg.Is` will not work at all. – David Tchepak Jan 02 '18 at 10:18
  • I didn't post the constructor of this test class in this post but it does call `_mongoContext = Substitute.For()` in order to create a mock and `_mongoContext.Find(expectedFilter, expectedProjection).Throws(_exception);` is using the extension method `Throws` which just calls `.Returns(x => throw exception)` so the use of `Arg.Is` is being use as shown in the documentation. – Nick Dichiaro Jan 18 '18 at 19:23
  • Is `MongoContext` in `GetRecordUserViews` the injected mock? Does the test work if you change the `_mongoContext.Find` stub to match any argument? – David Tchepak Jan 18 '18 at 20:53
  • Yes, `MongoContext` is inject set through injection. This test implemented the same as another test we are using when calling `Find()` with only a filter which works. `Arg.Any` does work but we would like at be able to test the actually projection that is being passed. The code below works as expected. `var filter = Builders.Filter.Eq(x => x.Id, _recordModel.Id); var expectedFilter = Arg.Is>( x => x.ToJson().Equals(filter.ToJson())); _mongoContext.Find(expectedFilter).Throws(_exception);` – Nick Dichiaro Jan 19 '18 at 15:30
  • Could you please [post a minimal reproduction to NSubstitute issues](https://github.com/nsubstitute/NSubstitute/issues/new)? I tried but was unable to reproduce it: https://gist.github.com/dtchepak/36271ec17dc6aee8e50cd21ff24a09b3 – David Tchepak Jan 20 '18 at 01:39

0 Answers0