stphivos / django-mock-queries

A library for mocking django queryset functions in memory for testing
MIT License
319 stars 53 forks source link

Using Django Q filters with mocked instances, doesn't work as expected! #90

Open mehdi-essebbar opened 6 years ago

mehdi-essebbar commented 6 years ago

THE METHOD BEING TESTED:



from repo.models import Expert from django.db.models import Q

def method_to_test(): experts = Expert.objects.filter(Q(firstname__iregex='^a.*')) return experts



THE TEST IMPLEMENTATION:



def test_q_expr(experts): @mocked_relations(Expert) def mocking_instances(qs):

    for exp in experts:
        m = MockModel(firstname='a' + exp.firstname, user=exp.user)
        Expert.objects.add(m)

    vals = method_to_test()
    print(len(vals))
    return vals

results = mocking_instances()
print(len(results))
assert False


THE ERROR OUTPUT:



==================================== FAILURES ==================================== __ test_q_expr ___

experts = [<Expert: Zmzhn 70214>, , ]

def test_q_expr(experts):
    @mocked_relations(Expert)
    def mocking_instances(qs):

        for exp in experts:
            m = MockModel(firstname='a' + exp.firstname, user=exp.user)
            Expert.objects.add(m)

        vals = method_to_test()
        print(len(vals))
        return vals

  results = mocking_instances()

tests/test_immodvisor.py:48:


../../.venv/local/lib/python2.7/site-packages/mock/mock.py:1305: in patched return func(*args, *keywargs) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/mocks.py:299: in absorb_mocks return target(test_case) tests/test_immodvisor.py:42: in mocking_instances vals = method_to_test() tests/test_immodvisor.py:30: in method_to_test experts = Expert.objects.filter(Q(firstname__iregex='^a.')) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/query.py:108: in filter results = self._filter_q(results, x) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/query.py:96: in _filter_q results = merge(results, filtered) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/utils.py:13: in merge return first + list(set(second) - set(first)) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/query.py:415: in hash return hash_dict(self) ../../.venv/local/lib/python2.7/site-packages/django_mock_queries/utils.py:282: in hash_dict return hash(tuple(sorted((k, v) for k, v in obj_values.items() if not fields or k in fields)))


self =

def __hash__(self):
    if self._get_pk_val() is None:
       raise TypeError("Model instances without primary key value are unhashable")

E TypeError: Model instances without primary key value are unhashable

../../.venv/local/lib/python2.7/site-packages/django/db/models/base.py:615: TypeError 1 failed in 0.65 seconds

stphivos commented 6 years ago

From m = MockModel(firstname='a' + exp.firstname, user=exp.user) does exp.user have a pk/id? It should work if that is populated or if the user=... part is omitted.

The library determines the hash value of a MockModel by hashing a tuple consisting of all attribute key/value pairs. If one of those values such as user happens to be a Django model, it ends up calling Django's Model.__hash__ which expects the model to have a pk.

So I think the best way is to change the library's code to determine a Model's hash depending on: