Unit Testing a Django Form with a FileField

DjangoUnit TestingDjango Forms

Django Problem Overview


I have a form like:

#forms.py
from django import forms

class MyForm(forms.Form):
    title = forms.CharField()
    file = forms.FileField()


#tests.py
from django.test import TestCase
from forms import MyForm

class FormTestCase(TestCase)
    def test_form(self):
        upload_file = open('path/to/file', 'r')
        post_dict = {'title': 'Test Title'}
        file_dict = {} #??????
        form = MyForm(post_dict, file_dict)
        self.assertTrue(form.is_valid())

How do I construct the file_dict to pass upload_file to the form?

Django Solutions


Solution 1 - Django

So far I have found this way that works

from django.core.files.uploadedfile import SimpleUploadedFile
 ...
def test_form(self):
        upload_file = open('path/to/file', 'rb')
        post_dict = {'title': 'Test Title'}
        file_dict = {'file': SimpleUploadedFile(upload_file.name, upload_file.read())}
        form = MyForm(post_dict, file_dict)
        self.assertTrue(form.is_valid())

Solution 2 - Django

May be this is not quite correct, but I'm creating image file in unit test using StringIO:

imgfile = StringIO('GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00'
                     '\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
imgfile.name = 'test_img_file.gif'

response = self.client.post(url, {'file': imgfile})

Solution 3 - Django

Here's another way that doesn't require you to use an actual image.

EDIT: Updated for Python 3.

from PIL import Image
from io import BytesIO # Python 2: from StringIO import StringIO
from django.core.files.uploadedfile import InMemoryUploadedFile

...

def test_form(self):
    im = Image.new(mode='RGB', size=(200, 200)) # create a new image using PIL
    im_io = BytesIO() # a BytesIO object for saving image
    im.save(im_io, 'JPEG') # save the image to im_io
    im_io.seek(0) # seek to the beginning

    image = InMemoryUploadedFile(
        im_io, None, 'random-name.jpg', 'image/jpeg', len(im_io.getvalue()), None
    )

    post_dict = {'title': 'Test Title'}
    file_dict = {'picture': image}

    form = MyForm(data=post_dict, files=file_dict)


Solution 4 - Django

The accepted answer has a downside, i.e. you always have to keep a dummy file in you environment for testing.

Assuming you are working in a team or in production, that is not very good practice. @xyres approach seems cleaner to me. It can be simplified more though.

As Python3 io docks mention, you can do something like:

loaded_file = BytesIO(b"some dummy bcode data: \x00\x01")
loaded_file.name = 'test_file_name.xls'

# and to load it in form
file_dict = {'file': SimpleUploadedFile(loaded_file.name, loaded_file.read())}

Full version

# Python3
from io import BytesIO
from django.core.files.uploadedfile import SimpleUploadedFile

class FormTestCase(TestCase)
    def test_form(self):
        # Simple and Clear
        loaded_file = BytesIO(b"some dummy bcode data: \x00\x01")
        loaded_file.name = 'test_file_name.xls'

        post_dict = {'title': 'Test Title'}
        file_dict = {'file': SimpleUploadedFile(loaded_file.name, loaded_file.read())}
        form = MyForm(post_dict, file_dict)
        self.assertTrue(form.is_valid())

It is also advised to utilize setUpTestData(cls) and setUp(self) class methods for data preparation. I personally found Mozilla's intro to unit testing very informative and straightforward.

Solution 5 - Django

Combined a few ideas from this and other Stack posts using Python 3. No external file or libraries required.

from django.core.files.uploadedfile import SimpleUploadedFile

png_hex = ['\x89', 'P', 'N', 'G', '\r', '\n', '\x1a', '\n', '\x00',
           '\x00', '\x00', '\r', 'I', 'H', 'D', 'R', '\x00',
           '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01',
           '\x08', '\x02', '\x00', '\x00', '\x00', '\x90',
           'w', 'S', '\xde', '\x00', '\x00', '\x00', '\x06', 'b', 'K',
           'G', 'D', '\x00', '\x00', '\x00', '\x00',
           '\x00', '\x00', '\xf9', 'C', '\xbb', '\x7f', '\x00', '\x00',
           '\x00', '\t', 'p', 'H', 'Y', 's', '\x00',
           '\x00', '\x0e', '\xc3', '\x00', '\x00', '\x0e', '\xc3',
           '\x01', '\xc7', 'o', '\xa8', 'd', '\x00', '\x00',
           '\x00', '\x07', 't', 'I', 'M', 'E', '\x07', '\xe0', '\x05',
           '\r', '\x08', '%', '/', '\xad', '+', 'Z',
           '\x89', '\x00', '\x00', '\x00', '\x0c', 'I', 'D', 'A', 'T',
           '\x08', '\xd7', 'c', '\xf8', '\xff', '\xff',
           '?', '\x00', '\x05', '\xfe', '\x02', '\xfe', '\xdc', '\xcc',
           'Y', '\xe7', '\x00', '\x00', '\x00', '\x00',
           'I', 'E', 'N', 'D', '\xae', 'B', '`', '\x82']

valid_png_bin = str.encode("".join(png_hex))
png = SimpleUploadedFile("test.png", valid_png_bin)

post_dict = {'title': 'Test Title'}
file_dict = {'picture': png}

form = MyForm(data=post_dict, files=file_dict)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJason ChristaView Question on Stackoverflow
Solution 1 - DjangoJason ChristaView Answer on Stackoverflow
Solution 2 - DjangodragoonView Answer on Stackoverflow
Solution 3 - DjangoxyresView Answer on Stackoverflow
Solution 4 - DjangoGr3atView Answer on Stackoverflow
Solution 5 - Djangosmooth-texanView Answer on Stackoverflow