66

I need to test the Photo model of my Django application. How can I mock the ImageField with a test image file?

tests.py

class PhotoTestCase(TestCase):

    def test_add_photo(self):
        newPhoto = Photo()
        newPhoto.image = # ??????
        newPhoto.save()
        self.assertEqual(Photo.objects.count(), 1)
Chillar Anand
  • 27,936
  • 9
  • 119
  • 136
Fabrizio A
  • 3,072
  • 5
  • 24
  • 35

9 Answers9

115

For future users, I've solved the problem. You can mock an ImageField with a SimpleUploadedFile instance.

test.py

from django.core.files.uploadedfile import SimpleUploadedFile

newPhoto.image = SimpleUploadedFile(name='test_image.jpg', content=open(image_path, 'rb').read(), content_type='image/jpeg')
phoenix
  • 7,988
  • 6
  • 39
  • 45
Fabrizio A
  • 3,072
  • 5
  • 24
  • 35
  • 2
    perfect, should be the accepted answer. Works in 1.10, thanks! – Laurent Sep 13 '16 at 07:16
  • 2
    @Norak Through some experimentation, I found that `name` is the filename you want to use for the image you read in and the `image_path` is the path of your image. For example `/path/to/image.jpg`. – Jeel Shah Nov 27 '17 at 19:12
  • setting content_type is optional. – Alex78191 Nov 17 '19 at 03:17
  • 3
    This way works but every time I run test the new image file is created in /media directory. – Dmytro Manzhula Mar 12 '21 at 18:38
  • 2
    Can you set a binary for the content ? `content=b"some content"` so that you don't have to have an actual image just for the tests ? – lbris Aug 23 '22 at 11:48
31

You can use a temporary file, using tempfile. So you don't need a real file to do your tests.

import tempfile

image = tempfile.NamedTemporaryFile(suffix=".jpg").name

If you prefer to do manual clean-up, use tempfile.mkstemp() instead.

Flimm
  • 136,138
  • 45
  • 251
  • 267
Purpleweb.fr
  • 411
  • 4
  • 4
26

Tell the mock library to create a mock object based on Django's File class

import mock
from django.core.files import File

file_mock = mock.MagicMock(spec=File, name='FileMock')

and then use in your tests

newPhoto.image = file_mock
Chillar Anand
  • 27,936
  • 9
  • 119
  • 136
24

If you don't want to create an actual file in the filesystem, you can use this 37-byte GIF instead, small enough to a be a bytes literal in your code:

from django.core.files.uploadedfile import SimpleUploadedFile

small_gif = (
    b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x00\x00\x00\x21\xf9\x04'
    b'\x01\x0a\x00\x01\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02'
    b'\x02\x4c\x01\x00\x3b'
)
uploaded = SimpleUploadedFile('small.gif', small_gif, content_type='image/gif')
Flimm
  • 136,138
  • 45
  • 251
  • 267
  • 4
    This yields a single white pixel. I found another gif, even smaller (only 35 bytes), which yields a single black pixel. If anyone is interested, it is: `b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x05\x04\x04\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b'` – ArtOfWarfare Oct 11 '20 at 01:40
13

Solution:

from StringIO import StringIO
# in python 3: from io import StringIO
from PIL import Image
from django.core.files.base import File

And create a static method in your TestCase class:

@staticmethod
def get_image_file(name='test.png', ext='png', size=(50, 50), color=(256, 0, 0)):
    file_obj = StringIO()
    image = Image.new("RGB", size=size, color=color)
    image.save(file_obj, ext)
    file_obj.seek(0)
    return File(file_obj, name=name)

Example:

instance = YourModel(name=value, image=self.get_image_file())
Sumithran
  • 6,217
  • 4
  • 40
  • 54
Maxim Panfilov
  • 371
  • 4
  • 15
3

You can do a few additional things to (1) avoid having to keep a dedicated test image around, and (2) ensure that all test files created during testing are deleted right after:

import shutil
import tempfile

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings

MEDIA_ROOT = tempfile.mkdtemp()

@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class MyTest(TestCase):

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(MEDIA_ROOT, ignore_errors=True)  # delete the temp dir
        super().tearDownClass()

    def test(self):
        img = SimpleUploadedFile('test.jpg', b'whatevercontentsyouwant')
        # ^-- this will be saved in MEDIA_ROOT
        # do whatever ...
adobles96
  • 53
  • 6
2

For someone to try upload-image test with python 3.xx

I fix little with Maxim Panfilov's excellent answer to make more dummy image with independent name.

from io import BytesIO
from PIL import Image
from django.core.files.base import File

#in your TestCase class:
class TestClass(TestCase):
    @staticmethod
    def get_image_file(name, ext='png', size=(50, 50), color=(256, 0, 0)):
        file_obj = BytesIO()
        image = Image.new("RGBA", size=size, color=color)
        image.save(file_obj, ext)
        file_obj.seek(0)
        return File(file_obj, name=name)

    def test_upload_image(self):
        c= APIClient()
        image1 = self.get_image('image.png')
        image2 = self.get_image('image2.png')
        data = 
            { 
                "image1": iamge1,
                "image2": image2,
            }
        response = c.post('/api_address/', data ) 
        self.assertEqual(response.status_code, 201) 
rumbarum
  • 803
  • 7
  • 7
1

My approach how to test model with no intention to pass any useful data:

from django.core.files import File
SomeModel.objects.create(image=File(file=b""))
Pavel Hanpari
  • 4,029
  • 2
  • 20
  • 23
0

If you use Factory Boy to generate your test data, that library handles this situation with an ImageField factory.

Here is a complete example. I'm assuming that all of these files are in the same Django app.

models.py example:

from django.db import models


class YourModel(models.Model):
    
    image = models.ImageField(upload_to="files/")

factories.py example:

import factory
from . import models


class YourModelFactory(factory.django.DjangoModelFactory):
    
    class Meta:
        model = models.YourModel
 
    image = factory.Django.ImageField()

tests.py example:

from django import test    
from . import factories


class YourModelTests(test.TestCase):
    
    def test_image_model(self):
        yourmodel = factories.YourModelFactory()
        self.assertIsNotNone(yourmodel.image)
Apreche
  • 30,042
  • 8
  • 41
  • 52