2

I have an aggregate Mongo query that projects some fields and calculates two other ones using $sum. The query works as expected, so I created an unit test for it, and to my surprise the test was failing.

I created a minimal, complete, and verifiable example to test my hypothesis that this was a problem with MongoMock, and it seems to be!

Here is the code:

import mongoengine as mongo
from mongoengine import connect
from mongoengine.queryset import QuerySet

class ValuesList(mongo.EmbeddedDocument):
    updated_value = mongo.DecimalField()


class ValuesHistory(mongo.Document):
    name = mongo.StringField()
    base_value = mongo.DecimalField()
    values_list = mongo.EmbeddedDocumentListField(ValuesList, required=False)
    meta = {
        'collection' : 'values_history'
    }

    def __str__(self):
        return 'name: {}\nbase_value: {}\n'.format(self.name, self.base_value)


def migrate_data(new_collection):
    ValuesHistory.objects.aggregate(
        {'$project': {'name': 1,
                     'base_value': {'$sum': ['$base_value', {'$arrayElemAt': ['$values_list.updated_value', -1]}]}
                      }
        },
        {'$out': "{}".format(new_collection)}
    )


def clear_tables_and_insert_test_data(db):
    db.test.values_history.drop()
    db.test.updated_values.drop()
    ValuesHistory(name='first',
                  base_value=100,
                  values_list=[ValuesList(updated_value=5),
                               ValuesList(updated_value=15)]).save()

def run_aggregate_query_with_db(db):
    new_collection_name = 'updated_values'

    migrate_data(new_collection_name)

    new_group = ValuesHistory.switch_collection(ValuesHistory(), new_collection_name)
    aggregated_values = QuerySet(ValuesHistory, new_group._get_collection()).all()
    for value in aggregated_values:
        print(value)

    db.close()

A quick explanation about the code above.

ValuesHistory is a class that contains a String name, a numeric base_value and a list of values (ValuesList class).

The method clear_tables_and_insert_test_data clears the two tables used in this test and inserts some test data.

The query in migrate_data method creates a new collection (through the $out operator) and the base_value of the newly created collection should be the sum of the current value and the last value in the values_list list. In my case it should be 115 (being 100 the current value and 15 the last value on the list).

If I run the code using a connection to my local MongoDB, like this:

if __name__ == '__main__':
    db = connect('test') # connect to real instance of Mongo
    clear_tables_and_insert_test_data(db)
    run_aggregate_query_with_db(db)

I get 115 as a result, which is exactly what is expected.

If I, instead, use a connection to MongoMock:

if __name__ == '__main__':
    db = connect('test', host='mongomock://localhost') # Connect to MongoMock instance
    clear_tables_and_insert_test_data(db)
    run_aggregate_query_with_db(db)

I get 100 as result, which is odd! Looks like the $sum operator did not do it's job properly, since the sum of 100 and 15 resulted in 100!

EDIT: I also tried using the $add operator, but the problem remains the same, yielding 100 when it should be 115.

TL;DR;

Question: How should I use $sum (or $add) inside an aggregate pipeline on MongoMock so that it yields the correct value?

gmauch
  • 1,316
  • 4
  • 25
  • 39
  • 1
    Can you share how you are setting up the mock data for this test please. I quick peruse of the [mongomock source](https://github.com/mongomock/mongomock/blob/9278169d42bb03ab50ce3ea5397efb9723bf3c58/mongomock/aggregate.py#L216-L219) shows to me that there is nothing special going on here and the `$arrayElemAt` is simply being interpreted as a `return array[index]`. So this can only be a matter of the data not being set up exactly like you think it is. – Neil Lunn Nov 21 '18 at 02:02
  • 1
    In short the "completeness" here is actually missing the "mock" part which you are saying comes out differently. But kudos on the rest. – Neil Lunn Nov 21 '18 at 02:05
  • @NeilLunn, I'm not sure I got your question right. In the example I gave above, I insert the data used for the test. I just refactored the code and edited the question to make it more explicit. Please let me know if it's clear enough now. – gmauch Nov 21 '18 at 12:12
  • My bad, I clearly missed that. So then at a glance it seems more likely that the `$sum` is actually the issue here. Would need a look at the source but it;s plausible the authors forgot to implement the newer 2.6 usage. Can you try `$add` here instead? That's basically the same thing, but since it's an older operator it's usage is more likely implemented properly. – Neil Lunn Nov 21 '18 at 19:55
  • @NeilLunn I just tested using $add, but the result is wrongly the same for Mongomock! Just to be sure, I also updated my Mongomock version to the latest 3.14.0 ( I was using 3.12.0) but the 100 result keeps showing up! Finally, I created an [issue on Mongomock project](https://github.com/mongomock/mongomock/issues/473), perhaps we'll get more info from there. – gmauch Nov 22 '18 at 12:23
  • @NeilLunn I just read that the [issue I opened on Mongomock project](https://github.com/mongomock/mongomock/issues/473) received 3 new commits and is now closed! I'll wait for the next version and hopefully the issue will be fixed. If it does, I'll write an answer to my own problem. – gmauch Nov 23 '18 at 12:35
  • I just read the comments. My advice would be to look for another mocking library, since that's a pretty uninformed response and if your issue just gets closed like that with no investigation ( and the "generated array" comment is actually laughable ) then I would not consider any maintainer competent enough to actually resolve the issue themselves. – Neil Lunn Nov 23 '18 at 20:07
  • @NeilLunn Would you please suggest other mocking libraries? A few questions here in SO, like [this](https://stackoverflow.com/questions/15915031/use-mock-mongodb-server-for-unit-test) and [this](https://stackoverflow.com/questions/42239241/how-to-mock-mongodb-for-python-unittests), shows that mongomock is a common choice. It never hurts to know and test different libraries, but so far mongomock has been working for me. In this specific problem I had, the fix was somewhat quick, which I find a good thing, even though the error was in a basic functionality for me. Thanks for the help so far! – gmauch Nov 26 '18 at 20:37

1 Answers1

1

The problem described was, indeed, a bug on Mongomock that existed up to version 3.14.0.

After the original question was posted, I opened an issue on Mongomock's github describing the problem. Shortly after it was fixed and version 3.15.0 has been released a few days ago. I ran the code on the question and the issue is now solved for both $add and $sum operators!

TL;DR

Updating to Mongomock 3.15.0 is enough to solve the problem.

gmauch
  • 1,316
  • 4
  • 25
  • 39