Django testing model with ImageField
Question:
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)
Answers:
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
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')
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())
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.
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'x47x49x46x38x39x61x01x00x01x00x00x00x00x21xf9x04'
b'x01x0ax00x01x00x2cx00x00x00x00x01x00x01x00x00x02'
b'x02x4cx01x00x3b'
)
uploaded = SimpleUploadedFile('small.gif', small_gif, content_type='image/gif')
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""))
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)
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 ...
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)
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)
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
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')
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())
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.
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'x47x49x46x38x39x61x01x00x01x00x00x00x00x21xf9x04'
b'x01x0ax00x01x00x2cx00x00x00x00x01x00x01x00x00x02'
b'x02x4cx01x00x3b'
)
uploaded = SimpleUploadedFile('small.gif', small_gif, content_type='image/gif')
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""))
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)
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 ...
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)