3

I want to test my handling of a MongoWriteException using the Mongo driver, here is a sample method:

    private void Update()
    {
        try
        {
            var find = Builders<Filter>.Filter.Eq(e => e.Id, "someId");
            var update = Builders<Filter>.Update.Set(e => e.SomeValue, "AValue");
            _documentStore.MongoCollection<Filter>().UpdateOne(find, update, new UpdateOptions { IsUpsert = true }, CancellationToken.None);
        }
        catch (MongoWriteException mongoWriteException)
        {
            if (mongoWriteException.WriteError.Category != ServerErrorCategory.DuplicateKey)
            {
                throw;
            }
        }
    }

Does anyone know how I can mock a MongoWriteException? I tried to construct it like so:

var mongoWriteException = new MongoWriteException(new ConnectionId(new ServerId(new ClusterId(1), new DnsEndPoint("d", 2)), 0), new WriteError(), // <- Protected constructor

But the WriteError class has an internal constructor

Mark Walsh
  • 3,241
  • 1
  • 24
  • 46
  • I'm not that much familiar with mongodb. what type is `_documentStore`. I think you may need to abstract you access to the driver is it isn't already. – Nkosi Sep 08 '16 at 14:05
  • Document store is just a facade. The real problem is the fact that I can't simulate the exception occurring. – Mark Walsh Sep 08 '16 at 17:55
  • Where would it normally occur. may be you can use moq to throw it when you call one of the mocks. – Nkosi Sep 08 '16 at 18:09
  • I need to be able to access the WriteError which is a protected constructor, so it is not possible. – Mark Walsh Sep 08 '16 at 20:58
  • @MarkWalsh, I can say that it's bad way, but you can construct object of WriteError type via reflection. – Artavazd Balayan Sep 12 '16 at 20:56
  • [This](http://stackoverflow.com/a/10323930/4045532) shows how you can do what you need to using MOQ, but you would need to ensure that whatever instance you want to throw the exception is injected in the constructor so that you can mock it – Corporalis Sep 14 '16 at 14:05
  • How can I mock something which is Internal? I can make my _documentStore throw an exception, that's easy, I just can't construct the expected exception because of internal constructors. – Mark Walsh Sep 14 '16 at 14:23
  • Sorry, my mistake. Unfortunately I don't currently have access to any mongo assemblies, but what about creating an instance of BulkWriteError which is a sub class of WriteError? – Corporalis Sep 14 '16 at 14:37

4 Answers4

8

A small example based on the driver's own tests but using reflection to get to the internal constructors

static class MockMongoCollection // : IMongoCollection<TDocument>
{
    private static readonly MongoWriteException __writeException;

    static MockMongoCollection()
    {
        var connectionId = new ConnectionId(new ServerId(new ClusterId(1), new DnsEndPoint("localhost", 27017)), 2);
        var innerException = new Exception("inner");
        var ctor = typeof (WriteConcernError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
        var writeConcernError = (WriteConcernError)ctor.Invoke(new object[] { 1, "writeConcernError", new BsonDocument("details", "writeConcernError") });
        ctor = typeof (WriteError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
        var writeError = (WriteError) ctor.Invoke(new object[] {ServerErrorCategory.Uncategorized, 1, "writeError", new BsonDocument("details", "writeError")});
        __writeException = new MongoWriteException(connectionId, writeError, writeConcernError, innerException);
    }

    public static void UpdateOne()
    {
        throw __writeException;
    }
}

class ExampleTests
{
    [Test]
    public void UncategorizedWriteExceptionTest()
    {
        Assert.Throws<MongoWriteException>(MockMongoCollection.UpdateOne);
    }
}

There is also a constructor using SerializationInfo which may have a similar smell.

logan rakai
  • 2,519
  • 2
  • 15
  • 24
0

You can create the object of a class with Internal constructor using reflection.

Something like

 var obj = Activator.CreateInstance(typeof(WriteError), true);

The second parameter in the above code is to specify the Activator to look for non public default constructors.

But this way you cannot initialize any values or use Parametered constructors.

I am assuming you have created a fake assembly for mogo DB library and using shim to mock the UpdateOne method.

if that the case, you can Shim the WriteError object and make the property "Category" return any value you desire according to the test case.

It would be something like

ShimsWriteError.AllInstances.Category = errorObj => ServerErrorCategory.DuplicateKey

The syntax may be different in the above code. but the idea is same.

Binu Vijayan
  • 803
  • 1
  • 9
  • 24
0

So I took @logan rakai's answer here (https://stackoverflow.com/a/39497316/1001408) and changed it a bit. Here is what I ended up with.

    [Test]
    public void GivenADuplicateKeyWriteErrorOccurs_WhenCallingUpdateOne_ThenNoExceptionIsThrown()
    {
        // Given
        var someMongoService = CreateSomeObject();

        _mockMongoCollection.Setup(x => x.UpdateOne(It.IsAny<FilterDefinition<SomeObject>>(), It.IsAny<UpdateDefinition<SomeObject>>(), It.IsAny<UpdateOptions>(), default(CancellationToken))).Throws(CreateMongoWriteException(ServerErrorCategory.DuplicateKey));

        // When / Then
        Assert.DoesNotThrow(() => someMongoService.Upsert(new CreateNewSomeObject());
    }

    [Test]
    public void GivenAExceptionOccursWhichIsNotADuplicateKeyWriteError_WhenCallingUpdateOne_ThenTheExceptionIsThrown()
    {
        // Given
        var someMongoService = CreateFilterService();

        var exception = CreateMongoWriteException(ServerErrorCategory.ExecutionTimeout);
        _mockMongoCollection.Setup(x => x.UpdateOne(It.IsAny<FilterDefinition<SomeObject>>(), It.IsAny<UpdateDefinition<SomeObject>>(), It.IsAny<UpdateOptions>(), default(CancellationToken))).Throws(exception);

        // When / Then
        Assert.Throws<MongoWriteException>(() => someMongoService.Upsert(new CreateNewSomeObject());
    }

    public static MongoWriteException CreateMongoWriteException(ServerErrorCategory serverErrorCategory)
    {
        var connectionId = new ConnectionId(new ServerId(new ClusterId(1), new DnsEndPoint("localhost", 27017)), 2);
        
        var writeConcernError = CreateWriteConcernError();
        var writeError = CreateWriteError(serverErrorCategory);
        return new MongoWriteException(connectionId, writeError, writeConcernError, new Exception());
    }

    private static WriteError CreateWriteError(ServerErrorCategory serverErrorCategory)
    {
        var ctor = typeof (WriteError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
        var writeError = (WriteError)ctor.Invoke(new object[] {serverErrorCategory, 1, "writeError", new BsonDocument("details", "writeError")});
        return writeError;
    }

    private static WriteConcernError CreateWriteConcernError()
    {
        var ctor = typeof(WriteConcernError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
        return (WriteConcernError)ctor.Invoke(new object[] { 1, "writeConcernError", new BsonDocument("details", "writeConcernError") });
    }

Edit: And here are the required namespaces for those of us with lesser IDEs

using System;
using System.Net;
using System.Reflection;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Clusters;
using MongoDB.Driver.Core.Connections;
using MongoDB.Driver.Core.Servers;
Community
  • 1
  • 1
Mark Walsh
  • 3,241
  • 1
  • 24
  • 46
0
var connectionId = new ConnectionId(new ServerId(new ClusterId(1), new DnsEndPoint("localhost", 27017)), 2);
var innerException = new Exception("inner");
var ctor = typeof(WriteConcernError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
var writeConcernError = (WriteConcernError)ctor.Invoke(new object[] { 1, "writeConcernError", "writeConcernError", new BsonDocument("details", "writeConcernError"), new List<string>() });
ctor = typeof(WriteError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
var writeError = (WriteError)ctor.Invoke(new object[] { ServerErrorCategory.Uncategorized, 1, "writeError", new BsonDocument("details", "writeError") }); 
var exception = new MongoWriteException(connectionId, writeError, writeConcernError, innerException);

logan rakai's answer has changed slightly and these are the latest objects you'll need to pass in for reflection

Davy-F
  • 123
  • 1
  • 2
  • 12