2

The Mock testing library is the one Django topic I just can't seem to wrap my head around. For example, in the following code, why don't the mock User instances that I create in my unit test appear in the User object that I query in the 'get_user_ids' method? If I halt the test in the 'get_user_ids' method via the debug call and do "User.objects.all()", there's nothing in the User queryset and the test fails. Am I not creating three mock User instances that will be queried the the UserProxy's static method?

I'm using Django 1.6 and Postgres 9.3 and running the test with the command "python manage.py test -s apps.profile.tests.model_tests:TestUserProxy".

Thanks!

# apps/profile/models.py
from django.contrib.auth.models import User
class UserProxy(User):
    class Meta:
        proxy = True

    @staticmethod
    def get_user_ids(usernames):
        debug()
        user_ids = []
        for name in usernames:
            try:
                u = User.objects.get(username__exact=name)
                user_ids.append(u.id)
            except ObjectDoesNotExist:
                logger.error("We were unable to find '%s' in a list of usernames." % name)
        return user_ids


# apps/profile/tests/model_tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock
from apps.profile.models import UserProxy

class TestUserProxy(TestCase):
    def test_get_user_ids(self):
        u1 = Mock(spec=User)
        u1.id = 1
        u1.username = 'user1'

        u2 = Mock(spec=User)
        u2.id = 2
        u2.username = 'user2'

        u3 = Mock(spec=User)
        u3.id = 3
        u3.username = 'user3'

        usernames = [u1.username, u2.username, u3.username]
        expected = [u1.id, u2.id, u3.id]
        actual = UserProxy.get_user_ids(usernames)
        self.assertEqual(expected, actual)
1
  • I was reading your question again and your comments to the other post. You want create a real user and not mocks: you need to create the real object by User() like in docs.djangoproject.com/en/1.7/intro/tutorial05/… . Mock() are mocks and not wrapers. You should change your question to be clear... Two of as have misunderstood what you want... Commented Jan 8, 2015 at 22:18

1 Answer 1

8

Mocking is awesome for testing, and can lead to very clean tests, however it suffers a little from (a) being a bit fiddly to get ones head around when starting out, and (b) does require some effort often to set up mock objects and have then injected/used in the correct places.

The mock objects you are creating for the users are objects that look like a Django User model object, but they are not actual model objects, and therefore do not get put into the database.

To get your test working, you have two options, depending on what kind of test you want to write.

Unit Test - Mock the data returned from the database

The first option is to get this working as a unit test, i.e. testing the get_user_ids method in isolation from the database layer. To do this, you would need to mock the call to User.objects.get(username__exact=name) so that it returns the three mock objects you created in your test. This would be the more correct approach (as it is better to test units of code in isolation), however it would involve more work to set up than the alternative below.

One way to achieve this would be to firstly separate out the user lookup into it's own function in apps/profile/models.py:

def get_users_by_name(name):
    return User.objects.get(username__exact=name)

This would need to be called in your function, by replacing the call to Users.objects.get(username__exact=name) with get_users_by_name(name). You can then modify your test to patch the function like so:

from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock, patch
from apps.profile.models import UserProxy

class TestUserProxy(TestCase):

    @patch('apps.profile.models.get_user_by_name')
    def test_get_user_ids(self, mock_get_user_by_name):
        u1 = Mock(spec=User)
        u1.id = 1
        u1.username = 'user1'

        u2 = Mock(spec=User)
        u2.id = 2
        u2.username = 'user2'

        u3 = Mock(spec=User)
        u3.id = 3
        u3.username = 'user3'

        # Here is where we wire up the mocking - we take the patched method to return
        # users and tell it that, when it is called, it must return the three mock
        # users you just created.
        mock_get_user_by_name.return_value = [u1, u2, u3]

        usernames = [u1.username, u2.username, u3.username]
        expected = [u1.id, u2.id, u3.id]
        actual = UserProxy.get_user_ids(usernames)
        self.assertEqual(expected, actual)

Integration Test - Create real user objects

The second approach is to modify this to be an integration test, i.e. one that tests both this unit of code and also the interaction with the database. This is a little less clean, in that you are now exposing your tests on the method to the chance of failing because of problems in a different unit of code (i.e. the Django code that interacts with the database). However, this does make the setup of the test a lot simpler, and pragmatically may be the right approach for you.

To do this, simply remove the mocks you have created and create actual users in the database as part of your test.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your effort. While your first approach is instructive, I don't think it's the solution I was seeking as its focus is on generating mock Users to populate the usernames list rather than creating an actual mock User object that's accessible by the get_user_ids method. Your second answer is closer to the mark and the reason I'm giving you credit. However, rather than create a User model instance, I used factory_boy with its create strategy (because I need the user IDs) to create a user instance that's visible to the method in question.
I think you're making the right choice - the first option is the purist approach, the second option is more pragmatic and the one that i would take. Using mocks is incredibly powerful but for this specific use case using real user objects will lead to simpler and more readable tests.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.