Giter Site home page Giter Site logo

django-mock-queries's Introduction

Latest Version Build Status Code Coverage Code Climate

Django Mock Queries

A library for mocking Django queryset functions in memory for testing

Features

  • QuerySet style support for method chaining
  • Filtering with Q objects
  • Aggregates generation
  • CRUD functions
  • Field lookups
  • django-rest-framework serializer asserts

Examples

from django.db.models import Avg, Q
from django_mock_queries.query import MockSet, MockModel

qs = MockSet(
    MockModel(mock_name='john', email='[email protected]'),
    MockModel(mock_name='jeff', email='[email protected]'),
    MockModel(mock_name='bill', email='[email protected]'),
)

print([x for x in qs.all().filter(email__icontains='gmail.com').select_related('address')])
# Outputs: [john, bill]

qs = MockSet(
    MockModel(mock_name='model s', msrp=70000),
    MockModel(mock_name='model x', msrp=80000),
    MockModel(mock_name='model 3', msrp=35000),
)

print(qs.all().aggregate(Avg('msrp')))
# Outputs: {'msrp__avg': 61666}

qs = MockSet(
    MockModel(mock_name='model x', make='tesla', country='usa'),
    MockModel(mock_name='s-class', make='mercedes', country='germany'),
    MockModel(mock_name='s90', make='volvo', country='sweden'),
)

print([x for x in qs.all().filter(Q(make__iexact='tesla') | Q(country__iexact='germany'))])
# Outputs: [model x, s-class]

qs = MockSet(cls=MockModel)
print(qs.create(mock_name='my_object', foo='1', bar='a'))
# Outputs: my_object

print([x for x in qs])
# Outputs: [my_object]

Test function that uses Django QuerySet:

from unittest import TestCase
from unittest.mock import patch
from django_mock_queries.query import MockSet, MockModel


class TestApi(TestCase):
    """
    Test function applies expected filters by patching Django's user model Manager or Queryset with a MockSet.
    """
    users = MockSet()
    user_objects = patch('django.contrib.auth.models.User.objects', users)
    
    def active_users(self):
        """
        Query active users.
        """
        return User.objects.filter(is_active=True).all()

    @user_objects
    def test_api_active_users_filters_by_is_active_true(self):
        self.users.add(
            MockModel(mock_name='active user', is_active=True),
            MockModel(mock_name='inactive user', is_active=False)
        )

        for user in self.active_users():
            self.assertTrue(user.is_active)

Test django-rest-framework model serializer:

class CarSerializer(serializers.ModelSerializer):
    """
    Car model serializer that includes a nested serializer and a method field.
    """
    make = ManufacturerSerializer()
    speed = serializers.SerializerMethodField()

    def get_speed(self, obj):
        return obj.format_speed()

    class Meta:
        model = Car
        fields = ('id', 'make', 'model', 'speed',)


def test_car_serializer_fields(self):
    """
    Test serializer returns fields with expected values and mock the result of nested serializer for field `make`.
    """
    car = Car(id=1, make=Manufacturer(id=1, name='vw'), model='golf', speed=300)

    values = {
        'id': car.id,
        'model': car.model,
        'speed': car.formatted_speed(),
    }

    assert_serializer(CarSerializer) \
        .instance(car) \
        .returns('id', 'make', 'model', 'speed') \
        .values(**values) \
        .mocks('make') \
        .run()

Full Example

There is a full Django application in the examples/users folder. It shows how to configure django_mock_queries in your tests and run them with or without setting up a Django database. Running the mock tests without a database can be much faster when your Django application has a lot of database migrations.

To run your Django tests without a database, add a new settings file, and call monkey_patch_test_db(). Use a wildcard import to get all the regular settings as well.

# settings_mocked.py
from django_mock_queries.mocks import monkey_patch_test_db

from users.settings import *

monkey_patch_test_db()

Then run your Django tests with the new settings file:

./manage.py test --settings=users.settings_mocked

Here's the pytest equivalent:

pytest --ds=users.settings_mocked

That will run your tests without setting up a test database. All of your tests that use Django mock queries should run fine, but what about the tests that really need a database?

ERROR: test_create (examples.users.analytics.tests.TestApi)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/.../examples/users/analytics/tests.py", line 28, in test_create
    start_count = User.objects.count()
  [...]
NotSupportedError: Mock database tried to execute SQL for User model.

If you want to run your tests without a database, you need to tell Django to skip the tests that need a database. You can do that by putting a skip decorator on the test classes or test methods that need a database.

@skipIfDBFeature('is_mocked')
class TestApi(TestCase):
    def test_create(self):
        start_count = User.objects.count()

        User.objects.create(username='bob')
        final_count = User.objects.count()

        self.assertEqual(start_count + 1, final_count)

Installation

pip install django_mock_queries

Contributing

Anything missing or not functioning correctly? PRs are always welcome! Otherwise, you can create an issue so someone else does it when time allows.

You can follow these guidelines:

  • Fork the repo from this page
  • Clone your fork:
git clone https://github.com/{your-username}/django-mock-queries.git
cd django-mock-queries
git checkout -b feature/your_cool_feature
  • Implement feature/fix
  • Add/modify relevant tests
  • Run tox to verify all tests and flake8 quality checks pass
tox
  • Commit and push local branch to your origin
git commit . -m "New cool feature does this"
git push -u origin HEAD
  • Create pull request

TODO

  • Add docs as a service like readthedocs with examples for every feature
  • Add support for missing QuerySet methods/Field lookups/Aggregation functions:
    • Methods that return new QuerySets: annotate, reverse, none, extra, raw
    • Methods that do not return QuerySets: bulk_create, in_bulk, as_manager
    • Field lookups: search
    • Aggregation functions: StdDev, Variance

django-mock-queries's People

Contributors

1oglop1 avatar allanlewis avatar brianzhou13 avatar dannywillems avatar dependabot[bot] avatar dmastylo avatar donkirkby avatar dvirh-qspark avatar gabriel-fontenelle avatar grabekm90 avatar harry-kim avatar ish-vasa avatar karally avatar lvieirajr avatar m3brown avatar mannysz avatar mapx avatar martingotelli avatar mdalp avatar orf avatar phitranphitranphitran avatar platonfloria avatar rbusquet avatar shinneider avatar stefan6419846 avatar stphivos avatar szykin avatar zuzelvp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-mock-queries's Issues

MockSet doesn't seem to copy all of its related model's properties.

View

from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.response import Response
from app.authentication import FirebaseAuthentication
from app.models import User
from app.serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

Test

def generate_fake_user(pk, name):
    return {
        'pk': pk,
        'email': '{}@example.com'.format(name),
        'password': 'password{}'.format(name),
    }

class UserViewSetTestCase(TestCase):
    users = MockSet(
        User(**generate_fake_user(1, 'A')),
        User(**generate_fake_user(2, 'B')))
    user_objects = patch(
        'app.models.user.User.objects.all',
        return_value=users)

    @user_objects
    def test_retrieve(self, usersMock):
        request = RequestFactory()
        request.user = AnonymousUser()
        result = UserViewSet().retrieve(request)

This raises

AttributeError: 'NoneType' object has no attribute 'DoesNotExist'

I'm trying to test the situation where no user can be found for the pk queried, and get_object_or_404 uses except queryset.model.DoesNotExist:
The problem with that queryset is a MockSet, which apparently doesn't handle model for parameter.

.create() method fails when model has foreign keys

When trying to create a mock model that has some PKs, it fails with:

ValueError: Cannot assign "mock-pizza-1": "MyModel.pizzas" must be a "Pizza" instance

Could have something to do with this. Is there a good way to do this ?

An implementation option would be to remove all relations from the save method, and putting them somewhere else, so when you try to access them, you access them from that place. This is a philosophy that is used in some DRF nested serializers.

Build is broken with pip 20.1

Collecting django-mock-queries
  Downloading django_mock_queries-2.1.4.tar.gz (19 kB)
    ERROR: Command errored out with exit status 1:
     command: ~/.pyenv/versions/3.5.9/envs/venv/bin/python3.5 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-install-a_7srd3z/django-mock-queries/setup.py'"'"'; __file__='"'"'/private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-install-a_7srd3z/django-mock-queries/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-pip-egg-info-wafekgr9
         cwd: /private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-install-a_7srd3z/django-mock-queries/
    Complete output (7 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-install-a_7srd3z/django-mock-queries/setup.py", line 9, in <module>
        req = [str(ir.req) for ir in install_req]
      File "/private/var/folders/zx/2wg3gb553mzf9qmv349dbczr0000gp/T/pip-install-a_7srd3z/django-mock-queries/setup.py", line 9, in <listcomp>
        req = [str(ir.req) for ir in install_req]
    AttributeError: 'ParsedRequirement' object has no attribute 'req'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

from pypa/pip#2286 (comment)

@dstufft: A setup.py which is importing pip is a broken setup.py already. We do not offer a programmatic API for people to use nor do we offer any backwards compatibility guarantees for any of the functions inside of pip.

Example in README:self.api.active_users():

I don't understand where self.api.active_users() is coming from (at the bottom of the example)

README:

""
Function that queries active users
"""
def active_users(self):
    return User.objects.filter(is_active=True).all()

"""
Test function applies expected filters by patching Django's user model Manager or Queryset with a MockSet
"""
from mock import patch
from django_mock_queries.query import MockSet, MockModel


class TestApi(TestCase):
    users = MockSet()
    user_objects = patch('django.contrib.auth.models.User.objects', users)

    @user_objects
    def test_api_active_users_filters_by_is_active_true(self):
        self.users.add(
        	MockModel(mock_name='active user', is_active=True),
        	MockModel(mock_name='inactive user', is_active=False)
        )

        for x in self.api.active_users():
        	assert x.is_active

Filter with empty Q object doesn't work as expected

Using an empty Q object in a Django queryset results in the full queryset being returned:

In [2]: User.objects.all().count()
Out[2]: 4827

In [3]: User.objects.filter(Q()).count()
Out[3]: 4827

In [4]: User.objects.filter(~Q()).count()
Out[4]: 4827

In [5]: User.objects.exclude(Q()).count()
Out[5]: 4827

In [6]: User.objects.exclude(~Q()).count()
Out[6]: 4827

This seems to work as expected for exclude(), but not for filter():

In [9]: n1 = MockModel(mock_name='item 1', pk=1)
In [10]: n2 = MockModel(mock_name='item 2', pk=2)
In [11]: mockset = MockSet(n1, n2)

In [12]: mockset.count()
Out[12]: 2

In [13]: mockset.filter(Q()).count()
Out[13]: 0

In [14]: mockset.filter(~Q()).count()
Out[14]: 0

In [15]: mockset.exclude(Q()).count()
Out[15]: 2

In [16]: mockset.exclude(~Q()).count()
Out[16]: 2

However, I think it only works as expected for exclude() because filter() is returning 0.

This is a problem in cases where you are constructing a Q object dynamically and it can sometimes end up empty. I've spent a few hours trying to fix this but haven't gotten there completely. Part of the process was trying to simplify the logic by converting any lookups by attributes to use Q objects (i.e., filter(x=1, y=2) -> filter(Q(x=1, y=1))). Part of the issue is that this conversion causes filtering on MockSets with actual in-memory Django model instances to fail because it uses django_mock_queries.utils.merge, which tries to hash them, and fails since they don't have a PK.

I don't know if I'll be able to finish this, but I'll add a link to my current branch in case it's useful.

Limit in MockModel number inside MockSet?

It seems there is limit in the number of MockModel inside a MockSet?

If I have more than 11 MockModels in a MockSet, I got this error message. But when I cut down the MockModels into 11 or less, this error message disappear (and it is unrelated on which MockModel I remove, so I believe it is the number, not a particular MockModel error)

web_1    |   File "/usr/local/lib/python3.6/site-packages/mock/mock.py", line 409, in __new__
web_1    |     bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments
web_1    |   File "/usr/local/lib/python3.6/inspect.py", line 3004, in bind_partial
web_1    |     return args[0]._bind(args[1:], kwargs, partial=True)
web_1    |   File "/usr/local/lib/python3.6/inspect.py", line 2924, in _bind
web_1    |     'too many positional arguments') from None
web_1    | TypeError: too many positional arguments

Mocking serializer: ListSerializer object is not callable

In order to increase coverage, I'm trying to test my serializer methods, such as

serializer

from rest_framework import serializers
from app.models import Host, HostRole

class HostSerializer(serializers.ModelSerializer):
    hostrole_set = serializers.SerializerMethodField('get_main_hostrole')
    class Meta:
        model = Host
        fields = ('hostrole_set')

    def get_main_hostrole(self, host):
        qs = HostRole.objects.filter(role_type="main", host=host)
        serializer = HostRoleSerializer(instance=qs, many=True)
        return serializer.data

test

from django.test import TestCase
from app.serializers import HostSerializer, HostRoleSerializer, HostRole
from django_mock_queries.query import MockSet, MockModel
from unittest.mock import patch



HOST = MockModel(pk=1)
HOST_ROLES = MockSet(
    MockModel(
        pk=1,
        host=1,
        roletype='main',
        name='Main role',
    ),
    MockModel(pk=2, host=HOST, role_type='sub', name='Sub role'),
    model=HostRole,
)

class HostSerializerTestCase(TestCase):
    @patch('app.serializers.hosts.HostRole', objects=HOST_ROLES)
    def test_get_main_hostrole(self, mock_objects):
        self.assertEqual(
            HostSerializer().get_main_hostrole(HOST),
            {
                'pk': 1,
                'host': 1,
                'roletype': 'main',
                'name': 'Main role',
            },
        )

this results on the test failing with

AssertionError: [] != {'pk': 1, 'host': 1, 'roletype': 'main', 'name': 'Main role'} 

If I try to replace host=1 with host=HOST in the HOST_ROLES MockModel, it fails on
return serializer.data with this error:

/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py:514: in to_representation
    attribute = field.get_attribute(instance)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = PrimaryKeyRelatedField(read_only=True)
instance = {'pk': 1, 'host': {'pk': 1, 'save': <PropertyMock id='139917611941784'>, '_MockModel__meta': <django_mock_queries.quer...e_type': None, 'task': None, 'description': None, 'about': None,}

    def get_attribute(self, instance):
        if self.use_pk_only_optimization() and self.source_attrs:
            # Optimized case, return a mock object only containing the pk attribute.
            try:
                instance = get_attribute(instance, self.source_attrs[:-1])
>               value = instance.serializable_value(self.source_attrs[-1])
E               TypeError: 'NoneType' object is not callable

/usr/local/lib/python3.7/site-packages/rest_framework/relations.py:167: TypeError

Re-reading your doc, I tried using the assert_serializer method, but I'm unclear on the best way to use it. When adding a breakpoint after serializer = HostRoleSerializer(instance=qs, many=True):

ipdb> assert_serializer(serializer).instance(qs.first()).returns('pk', 'name').run()                      
*** TypeError: 'ListSerializer' object is not callable   

I just want my test to verify that the HostSerializer.get_main_hostrole(HOST) is gonna return the data from the host role with roletype='main'.

Did you spot a mistake in my test ? Or do you have a better idea on how to complete this test ? Thanks for the support

Q objects with negation

Is it possible to get queries like ~Q(field=value) and by extension, more complex queries like ~Q(field1=value1) | Q(field2=value2) to work?

Get error when using django cursor pagination

I'm using django cursor pagination for queryset pagination, and for testing my code I'm using the django-mock-queries package for mock queryset. I get this error when I get page two in queryset:
python3.10/site-packages/django_mock_queries/utils.py:145: in is_match
return {


  COMPARISON_LT: lambda: first < second if first is not None else False,
    COMPARISON_LTE: lambda: first <= second if first is not None else False,
    COMPARISON_IN: lambda: first in second if first is not None else False,
    COMPARISON_STARTSWITH: lambda: first.startswith(second),
    COMPARISON_ISTARTSWITH: lambda: first.lower().startswith(second.lower()),
    COMPARISON_ENDSWITH: lambda: first.endswith(second),
    COMPARISON_IENDSWITH: lambda: first.lower().endswith(second.lower()),
    COMPARISON_ISNULL: lambda: (first is None) == bool(second),
    COMPARISON_REGEX: lambda: re.search(second, first) is not None,
    COMPARISON_IREGEX: lambda: re.search(second, first, flags=re.I) is not None,
    COMPARISON_RANGE: lambda: second[0] <= first <= second[1]
}[comparison]()

E TypeError: '<' not supported between instances of 'int' and 'Value'

Is it possible to use a m2m field with .set() method?

I'm trying to mock out and test a m2m field get set in my test.
I'm also using model_bakery for fixtures

The code I want to test is:
provider_currency.supported_countries.set(supported_countries)

And my test code:

    provider_currency = baker.prepare(ProviderCurrency, provider=provider)
    countries = baker.prepare(Country)
    mock_countries = MockSet(countries, model=Country)

    with mocked_relations(ProviderCurrency): 
        provider_currency.supported_countries = mock_countries
        my_method()

It tells me AttributeError: Mock object has no attribute 'set' so it tells me my ProviderCurrency isn't getting returned properly I guess. I tried mocking out ProviderCurrency.objects.get_or_create(), but then I get AttributeError: Mock object has no attribute 'supported_countries'

UnorderedObjectListWarning raised even when ordering the MockSet

Hi there,
Django has this warning it raises when pagination is used on an unordered QuerySet:

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list

The way to solve this is to either add ordering = ("<some_field_name>",) in the model's Meta class, or to add an order_by clause to the QuerySet.

However, with a MockSet, adding a order_by doesn't solve this and the warning is still raised.

from django.core.paginator import Paginator
from django_mock_queries.query import MockSet, MockModel

qs = MockSet(
    MockModel(mock_name='john', email='[email protected]'),
    MockModel(mock_name='jeff', email='[email protected]'),
    MockModel(mock_name='bill', email='[email protected]'),
).order_by('name')

Paginator(qs, 10)
# => UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: None QuerySet.
<django.core.paginator.Paginator at 0x1110565c0>

I can try to help solving this if needed.

Thanks!
Simon

Error if use order_by random

I tried to mock queryset to simulate random order, but this generate a error
django.core.exceptions.FieldError: Cannot resolve keyword '?' into field. Choices are 'anonymous', 'comment', 'created '

How to reproduce:

my model

class Evaluation(models.Model):
    comment = models.TextField(max_length=500)
    created = models.DateTimeField(auto_now_add=True)
    anonymous = models.BooleanField(default=False)

my func

def random(queryset, random):
    if random:
        return queryset.order_by('?')
    return queryset

my test

qs = MockSet(
    Evaluation(comment='test4', anonymous=True),
    Evaluation(comment='test2', anonymous=False),
    Evaluation(comment='test3', anonymous=True),
    Evaluation(comment='test1', anonymous=False),
    model=Evaluation,
)

    with patch('consultation.models.Evaluation.objects.all', return_value=qs):
        rad_qs = random(Evaluation.objects.all())
        self.assertNotEqual(list(qs), list(rad_qs))

More direct test way

qs = MockSet(
    MockModel(mock_name='test1', email='[email protected]'),
    MockModel(mock_name='test2', email='[email protected]'),
    MockModel(mock_name='test3', email='[email protected]'),
    MockModel(mock_name='test4', email='[email protected]'),
)

qs.order_by('?')

README.md typo

Hello!

In your README, you use the function patch

    user_objects = patch('django.contrib.auth.models.User.objects', users)

without importing it. It is a bit confusing for people who just started digging into Django testing.

Thanks.

Use token-based approach for Codecov

CI tends to currently fail to upload coverage to Codecov and recommends using a token instead:

    {'detail': ErrorDetail(string='Rate limit reached. Please upload with the Codecov repository upload token to resolve issue. Expected time to availability: 1068s.', code='throttled')}
Error: 429 Client Error: Too Many Requests for url: https://codecov.io/upload/v2?commit=525ad5a9bc6d59142acf56dd2e9fc558c174a4af&branch=changelog&build_url=http%3A%2F%2Fgithub.com%2Fstphivos%2Fdjango-mock-queries%2Factions%2Fruns%2F9717552856&service=github-actions&build=9717552856&slug=stphivos%2Fdjango-mock-queries&package=py2.1.13
Tip: See all example repositories: https://github.com/codecov?query=example

Decorating a class with mocked_relations does not work with setUp

I have been decorating my TestCase classes with the mocked_relations decorator. Unfortunately I found that setUp is not patched, and even if it is the scope prevents it from reaching the test methods. Therefore, I have created a mixin which can be used in TestCases to provide mocked_relations in the entire TestCase using start() and stop():

class TestCaseMockQueriesMixin:
    models = None

    def __init__(self, *args, **kwargs):
        if not self.models:
            raise ValueError(
                'No models have been supplied. Usage: '
                'class SomeTestCase(TestCaseMockQueriesMixin.mocked_relations([ModelA, ...]), TestCase)'
            )
        super().__init__(*args, **kwargs)

    @classmethod
    def mocked_relations(cls, *models):
        cls.models = models
        return cls

    def _callSetUp(self):
        self.start_mocks()
        # noinspection PyProtectedMember
        super()._callSetUp()

    def start_mocks(self):
        patcher_chain = mocked_relations(*self.models)
        patcher_chain.start()
        self.addCleanup(patcher_chain.stop)

This works when used like this:

class SomeTestCase(TestCaseMockQueriesMixin.mocked_relations([ModelA, ...]), TestCase):
     ...

This way you can mock the managers in setUp and for example add to a M2M field, what you add in setUp still exists in the test methods.

I just wanted to share my finding, maybe it's useful for others. If interested I can make a PR to integrate it in the rest of the code, let me know :)

Review MockSet implementation for better inheritance behaviour

Hi!
Thanks for this awesome library.

I’m using a subclass of MockSet with some helper methods (like __iter__() for example) that use in my unit tests. The problem is there is a lot of MockSet methods (distinct(), order_by(), filter(), exclude()'…) that returns a another instance of the MockSet class instead of the type of the actual self (the sub class)... Because of that, I can’t use my helper methods on returned value.

I propose to use type(self) or self.__class__ or delegating to another method like get_mockset_class() instead of harcording MockSet everywhere.

Maybe I can submit a PR these days if you’re ok with this proposal.

Breaking changes in 2.1.2: ignored filtered elements

Hi,

Seems there is a breaking change in the filter implementation, maybe introduced 08b5d76.

I cannot reproduce with a simple example, but seems some items are ignored while using filter on a mocked QuerySet, even there should be filtered. Downgrading to 2.1.1 works.

I suppose it is when the value is an integer or any other type. You only want to check it is not None, not just if the value is True. I would suggest changing it in if first is not None

Maintenance status?

What is the current maintenance status of this project? As far as I can see, the latest change to the main branch happened about a year ago, while some PRs are open for a longer time. The last release happened 1.5 years ago.

CI does not seem to run anymore after the latest Travis changes(?). Currently, Python 2.7 is still supported, although being EOL for over 3 years now, while recent Django versions > 3.0 are not tested at all.

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

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>, <Expert: Tt2vj JBL94>, <Expert: F4vm7 JU0H6>]

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 = <User: testquqwlb>

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

Filtering by related model with custom `related_query_name` doesn't work

Suppose we have two models: Patient and Slot.

class Slot(Model):
   ...
   email = EmailField(...)
   patient = models.ForeignKey(
       'patients.Patient',
       related_name='slots',
       related_query_name='slot',
       ...
    )

And then we need to filter patients by related slot.

def get_patients():
   ...
   # patients is queryset of patients.Patient
   return patients.filter(slot__email__isnull=True)

It's simple and working code. But when we try to mock get_patients with MockSet of patients created with fabric we get an error.

django.core.exceptions.FieldError: Cannot resolve keyword 'slot' into field. Choices are ...'slots', ...

If we change filter to slots__email__isnull=True, it works in tests. But now we get an error in Django shell.

django.core.exceptions.FieldError: Cannot resolve keyword 'slots' into field. Choices are: ... 'slot', ...

It would be great to fix this! Thanks. 😊

Add support for "union"

I used this library in several scenarios and it worked flawlessly. However I discovered that the union method of the QuerySet is not explicitly implemented. That causes union to be a MagicMock and an empty QuerySet is returned.

Add support for Django 3

Hi,

I see in the requirements/core.txt that Django is limited to version 2. What could be done so Django 3.0 can be supported?

Contains comparison doesn't work as expected for PostgreSQL ArrayField

Defining a model with ArrayField supports contains filtering but should work differently from the string contains comparison. The comparison should support both fields

def test_mock_set_contains():
    from django.contrib.postgres.fields import ArrayField
    from django.db.models import IntegerField, Model
    from django_mock_queries.query import MockSet

    class TestModel(Model):
        x = ArrayField(IntegerField())

        class Meta:
            app_label = "prompt"

    set = MockSet(model=TestModel)
    assert len(set.filter(x__contains=[1])) == 0
    set.create(x=[1, 2, 3])
    assert len(set.filter(x__contains=[1])) == 1

ForeignKey access attribute.

Hi,
First of all, thanks for sharing this! Is simply amazing to be able to mock QuerySet in few simple lines!

Saying that, I am trying to replicate a ForeignKey relation and access to its attribute. I understand it might not be supported by the library but do you have any suggestion to replicate such behavior?

Thanks a lot

from django_mock_queries.query import MockSet, MockModel


OPERATOR = MockSet(
    MockModel(
        name="Name_1",
        surname="Surname_1"
    )
)

TICKETS = MockSet(
    MockModel(
        change_id=123456,
        change_type="test",
        status="implementation",
        operator=OPERATOR,
        date_diff=-10
        ),
)

for x in TICKETS.all():
    op = x.operator_set.all()
    print(op)

The above returns:

AttributeError: 'NoneType' object has no attribute 'all'

Loosen model-bakery dependency

In my project, I'd like to use the latest version of model-bakery, but this package pins it to v1.0.2; I see this has been updated on master, but it's still pinned to v1.1. Would it be possible to loosen this to 1.x?

`.distinct()` when using a regular model: 'ModelName has no attribute items`

When trying to run the following I get ModelA has no attribute items:

class ModelA(models.Model):
  b = models.ForeignKey(ModelB)
   ...
class ModelB(models.Model):
  ...

def some_func():
    ModelA.objects.filter(
        b__in=...
    ).distinct('some_field').count()
src/pkg/test.py:11: in quantity
    ).distinct('some_field').count()
/usr/local/lib/python3.6/site-packages/mock/mock.py:1062: in __call__
    return _mock_self._mock_call(*args, **kwargs)
/usr/local/lib/python3.6/site-packages/mock/mock.py:1128: in _mock_call
    ret_val = effect(*args, **kwargs)
/usr/local/lib/python3.6/site-packages/django_mock_queries/query.py:171: in distinct
    key = hash_dict(item, *fields)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

obj = <ModelA: None>, fields = ('some_field',)

    def hash_dict(obj, *fields):
>       return hash(tuple(sorted((k, v) for k, v in obj.items() if not fields or k in fields)))
E       AttributeError: 'ModelA' object has no attribute 'items'

Looking in MockSet:distinct it loops through an items property, which doesn't exist on a regular model.

Is there any way to pass in ModelA to the MockSet instead of a MockModel?

mock_set.add(
  ModelA(some_field='a')
)

instead of

mock_set.add(
  MockModel(some_field='a')
)

Release date 2.1.6

#125

When are You plan to release version including that fix ?
It's a a problem also for Python 3.6

Values list doesn't act as expected

When calling values_list on a MockSet is expected a list containing the attributes provided as parameter. This is Django's behavior.

Currently calling values_list is returning a MockSet.

Changelog lags behind

The latest version inside the changelog is v1.0.5, while the latest release actually is v2.1.7. We should probably sync this up.

how can we mock a model instance?

Hi. I'd like to test a model method, while mocking the data of a test instance.
Using django wagtail and pytest.

models.py

class BlockPage(Page):  # A wagtail model
    title = models.CharField(max_length=100)
    block = models.ForeignKey()

   def get_context(self, request):
        context = super().get_context(request, *args, **kwargs)
        current_category = self.block.category
        context['category'] = current_category
        return context

conftest.py

@pytest.fixture(scope="class")
def make_block_page(request: _pytest.fixtures.FixtureRequest) -> None:
    def _make_block_page(cls: TestCase, title: str) -> dict:
        block = MockModel(mocktitle=title, category=MockModel())
        return MockModel(mock_name="BlockPage", title=title, block=block)

    # Make the method available from TestCase
    request.cls.make_block_page = _make_block_page

test_models.py

@pytest.mark.usefixtures("make_block_page")
class BlockPageTestCase(WagtailPageTests):
    def setUp(self) -> None:
        super().setUp()
        self.block_page = self.make_block_page("ブロック1")

    def test_context(self) -> None:
        fake_request = HttpRequest()
        context = BlockPage.get_context(self.block_page, fake_request)
        self.assertListEqual()

pytest traceback

============================== FAILURES ===============================
___________________ BlockPageTestCase.test_context ____________________

self = <models_test.BlockPageTestCase testMethod=test_context>

    def test_context(self) -> None:
        fake_request = HttpRequest()
>       context = BlockPage.get_context(self.block_page, fake_request)

gopart/tests/models_test.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = BlockPage, request = <HttpRequest>, args = (), kwargs = {}

    def get_context(self, request: HttpRequest, *args: list, **kwargs: dict) -> dict:
>       context = super().get_context(request, *args, **kwargs)
E       TypeError: super(type, obj): obj must be an instance or subtype of type

gopart/models.py:259: TypeError
===================== 1 failed, 1 passed in 0.27s =====================

I was hoping that passing mock_name="BlockPage" as parameter to MockModel would do the trick and the model would accept the mock as being an instance of itself, but it doesn't.

How would you go about fixing this issue?

Model.objects.create can't take an instance parameter

Hello,
I have been trying to use django-mock-queries with FactoryBoy and I had an issue when creating a set of instances via a DjangoModelFactory using a SubFactory

I made a repo to reproduce the issue:

https://github.com/benjaminrigaud/django-mock-queries-and-factory-boy

======================================================================
ERROR: test_mock (issue.main.tests.test_mock_factory_boy.FactoryBoyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/django_mock_queries/mocks.py", line 281, in absorb_mocks
    return target(test_case)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/django_mock_queries/mocks.py", line 281, in absorb_mocks
    return target(test_case)
  File "/home/me/projects/django-mock-queries-and-factory-boy/issue/main/tests/test_mock_factory_boy.py", line 29, in test_mock
    b2 = BFactoryWithSub()
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/factory/base.py", line 69, in __call__
    return cls.create(**kwargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/factory/base.py", line 623, in create
    return cls._generate(True, attrs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/factory/base.py", line 548, in _generate
    obj = cls._prepare(create, **attrs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/factory/base.py", line 523, in _prepare
    return cls._create(model_class, *args, **kwargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/factory/django.py", line 181, in _create
    return manager.create(*args, **kwargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/mock/mock.py", line 1062, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/mock/mock.py", line 1128, in _mock_call
    ret_val = effect(*args, **kwargs)
  File "/home/me/venvs/django-mock-queries-and-factory-boy/local/lib/python2.7/site-packages/django_mock_queries/query.py", line 199, in create
    raise ValueError('{} is an invalid keyword argument for this function'.format(k))
ValueError: a is an invalid keyword argument for this function

----------------------------------------------------------------------

I am digging a bit to understand the limitations.

Benefit and Numbers

Up to now the README does not explain the "Why?".

For you it is 100% clear, but for new developers they might not know the "Why", the benefits.

I guess your goal is to speed up tests.

OK, if you want to speed up tests, then it would make sense to present some numbers in the README.

How much faster are tests with your library?

Saving an already created instance with FKs (mocked with ModelMocker) produces an error

When mocking a Model, the mocked do_update of the ModelMocker produces an error since it's trying to edit the direct attribute instead of the pk:

    def _do_update(self, *args, **_):
        _, _, pk_val, values, _, _ = args
        objects = self.objects.filter(pk=pk_val)

        if objects.exists():
            attrs = {field.name: value for field, _, value in values if value is not None}
            self.objects.update(**attrs)
            return True
        else:
            return False

Solution: Instead of using the field.name, use the field.attname, for basic values fields the attname == name, and for FK, the attname== {name}_id

Code to reproduce (add it to the TestMockers class):

    def test_model_mocker_update_fk_from_instance(self):
        with ModelMocker(Manufacturer):
            with ModelMocker(Car, outer=False):
                manufacturer = Manufacturer.objects.create(name='foo')
                obj = Car.objects.create(speed=10, make=manufacturer)
                obj.make = Manufacturer.objects.create(name='bar')
                obj.save()

                self.assertEqual(Car.objects.get(pk=obj.id).make.name, 'bar')

Error:

ValueError: Cannot assign "2": "Car.make" must be a "Manufacturer" instance.

Sample with models.Manager

Hi, is there some example with mocked_relations and the models.Manager (objects=MiManager) because it doesnt reconized the function from the manger assigned (Mock object has no attribute ..) , thanks and this is a great library!

MockSet constructor limited to 11 elements on Python 3.8

Summary

There is a limit for mock querysets of 11 elements, where you the MockSet constructor throws a too many positional arguments error. This is a breaking issue for larger tests or for trying to test .iterator().

Note that we tested this in Python 3.7 and Python 3.8. It works in 3.7, but our environments use 3.8 on which it fails.

Expected result

It should be possible to specify larger querysets in Python 3.8.

Generic code to reproduce

from django_mock_queries.query import MockSet, MockModel
items = [MockModel(foo=i) for i in range(12)]

qs = MockSet(*items)

Error

/usr/lib/python3.8/unittest/mock.py:409: in __new__
    bound_args = sig.bind_partial(cls, *args, **kw).arguments
/usr/lib/python3.8/inspect.py:3032: in bind_partial
    return self._bind(args, kwargs, partial=True)

self = <Signature (self, spec=None, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs)>
args = (<class 'django_mock_queries.query.MockSet'>, {'foo': 0, 'bar': 1, 'foobar': 2, 'save': <PropertyMock id='139802296810...Mock id='139802296813648'>, '_MockModel__meta': <django_mock_queries.query.MockOptions object at 0x7f26423d7c70>}, ...), kwargs = {}

    def _bind(self, args, kwargs, *, partial=False):
        """Private method. Don't use directly."""
    
        arguments = OrderedDict()
    
        parameters = iter(self.parameters.values())
        parameters_ex = ()
        arg_vals = iter(args)
    
        while True:
            # Let's iterate through the positional arguments and corresponding
            # parameters
            try:
                arg_val = next(arg_vals)
            except StopIteration:
                # No more positional arguments
                try:
                    param = next(parameters)
                except StopIteration:
                    # No more parameters. That's it. Just need to check that
                    # we have no `kwargs` after this while loop
                    break
                else:
                    if param.kind == _VAR_POSITIONAL:
                        # That's OK, just empty *args.  Let's start parsing
                        # kwargs
                        break
                    elif param.name in kwargs:
                        if param.kind == _POSITIONAL_ONLY:
                            msg = '{arg!r} parameter is positional only, ' \
                                  'but was passed as a keyword'
                            msg = msg.format(arg=param.name)
                            raise TypeError(msg) from None
                        parameters_ex = (param,)
                        break
                    elif (param.kind == _VAR_KEYWORD or
                                                param.default is not _empty):
                        # That's fine too - we have a default value for this
                        # parameter.  So, lets start parsing `kwargs`, starting
                        # with the current parameter
                        parameters_ex = (param,)
                        break
                    else:
                        # No default, not VAR_KEYWORD, not VAR_POSITIONAL,
                        # not in `kwargs`
                        if partial:
                            parameters_ex = (param,)
                            break
                        else:
                            msg = 'missing a required argument: {arg!r}'
                            msg = msg.format(arg=param.name)
                            raise TypeError(msg) from None
            else:
                # We have a positional argument to process
                try:
                    param = next(parameters)
                except StopIteration:
                    raise TypeError('too many positional arguments') from None
                else:
                    if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
                        # Looks like we have no parameter for this positional
                        # argument
>                       raise TypeError(
                            'too many positional arguments') from None
E                       TypeError: too many positional arguments

/usr/lib/python3.8/inspect.py:2951: TypeError

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.