Unit testing Multiselect fields not loading as expected

64 views
Skip to first unread message

Trevor Woolley

unread,
May 5, 2017, 1:20:04 AM5/5/17
to Django users
Hi, when I run a unittest on a form containing a MultipleChoiceField, and in the unittest I test setting what I think should be a valid field, it does not pass. It appears that field in question extracts the data is uses for the test before the test database is created, and actually uses the production database for the values.

The result of the test and the (altered snippets) are below, if anyone can help I'd be grateful.

The below example includes a mock of the data fill function, but there is no difference in the ultimate response, if the mock setup is not used. If the production database contains the correct IDs the test passes.

Rgds
Trevor

#
# forms.py snippet
#
# appropriate imports

def get_my_list():
    """ Function to get the complete stuff list for multiselect """
    result = []
    my_stuff = Stuff.objects.all()
    for stuff in my_stuff:
        displayname = "%s (%s)" % (stuff.name1, stuff.name2)
        result.append({"id": str(stuff.id), "displayname": displayname})
    print("REAL --> %s" % result)
    return sorted(result, key=itemgetter("displayname"))


class DispLayIssueForm(forms.Form):
    """ Class to manage the changes / adds for the extra Stuff model """
    description = forms.CharField(
        label='Short Description',
        max_length=199,
        strip=True
    )
    my_choices = [(m['id'], m['displayname']) for m in get_my_list()]
    print("In Form --> %s" % my_choices)

    stuff = forms.MultipleChoiceField(
        widget=forms.SelectMultiple(attrs={'size': '10'}),
        choices=my_choices,
        label="My Stuff",
        required=False,
    )
    db_id = forms.CharField(widget=forms.HiddenInput)

    def clean(self):
        """ Form Clean function """
        # <Snip>

    def save(self):
        """ Form Save function """
        # <Snip>

    def update(self):
        """ Form Update function """
        # <Snip>


#
# test_forms
#
# appropriate imports
# django.test import TestCase


def mocked_get_my_list():
    """ Mocking the function so that it grabs the test data """
    result = []
    my_stuff = Stuff.objects.all()
    for stuff in my_stuff:
        displayname = "%s (%s)" % (stuff.name1, stuff.name2)
        result.append({"id": str(stuff.id), "displayname": displayname})
    print("MOCKED --> %s" % result)
    return result

class DispLayIssueFormTests(TestCase):
    """ Class to test the display issue form """

    def setUp(self):
        """ Initial setup just creates two stuff entries """
        self.name1 = create_stuff("foo1", "bar1")
        self.name2 = create_stuff("foo2", "bar2")

    def tearDown(self):
        """ Final tear downs """
        self.name1.delete()
        self.name2.delete()

    # Other tests that leave the stuff fields empty pass

    @mock.patch('form.get_my_list')
    def test_valid_save2(self, mock_list):
        """ Test to ensure a valid save succeeds - with stuff """
        print("START")
        mock_list.side_effect = mocked_get_my_list()
        testform = DispLayIssueForm({
            'description': 'test3',
            'stuff': [str(self.name1.id), ],
            'db_id': 0,
        })
        print("ERRORS --> %s" % testform.errors)
        print("FORMS --> %s" % testform)
        self.assertTrue(testform.is_valid())
        testform.save()
        dummy = ExtraStuff.objects.get(description='test3')

=====================
$ ./manage.py test issue.tests.test_forms.DispLayIssueFormTests.test_valid_save2
REAL --> []
In Form --> []
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
START
MOCKED --> [{'id': '1', 'displayname': 'foo1 (bar1)'}, {'id': '2', 'displayname': 'foo2 (bar2)'}]
ERRORS --> <ul class="errorlist"><li>stuff<ul class="errorlist"><li>Select a valid choice. 1 is not one of the available choices.</li></ul></li></ul>
FORMS --> <tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" value="test3" id="id_description" maxlength="199" required /></td></tr>
<tr><th><label for="id_stuff">Stuff:</label></th><td><ul class="errorlist"><li>Select a valid choice. 1 is not one of the available choices.</li></ul><select name="stuff" id="id_stuff" size="10" multiple="multiple"></select><input type="hidden" name="db_id" value="0" id="id_db_id" /></td></tr>
F
======================================================================
FAIL: test_valid_save2 (issue.tests.test_forms.DispLayIssueFormTests)
Test to ensure a valid save succeeds - with stuff
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\projects\issue\lib\site-packages\mock\mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "c:\projects\issue\issue\tests\test_forms.py", line 288, in test_valid_save2
    self.assertTrue(testform.is_valid())
AssertionError: False is not true

----------------------------------------------------------------------
Ran 1 test in 0.538s

FAILED (failures=1)
Destroying test database for alias 'default'...
$ ./manage.py shell
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import django
>>> django.get_version()
'1.11'
>>>

Trevor Woolley

unread,
May 7, 2017, 9:05:09 PM5/7/17
to Django users
Further investigation shows this as failing outside of a test scenario, and the "my_choices" is not being updated, unless the server (be that the ./manage.py runserver" or a production apache server) is restarted.

Rgds
Trevor

Tim Graham

unread,
May 8, 2017, 6:49:59 AM5/8/17
to Django users

This is because you have a module level query at my_choices = [(m['id'], m['displayname']) for m in get_my_list()].


There's a documentation warning about this; see the "Finding data from your production database when running tests?" note in this section [0].

[0] https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

Trevor Woolley

unread,
May 8, 2017, 6:52:01 PM5/8/17
to django...@googlegroups.com
Thanks Tim.

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/K0FDjDoVp1Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/20bf6c87-4e89-47d8-b689-79699d990593%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages