I have a project where there’s a model with a profile image field. This field is required.
I did not want to maintain an image file in the repository just for tests, so I decided to research other solutions.
Imagem file at model’s creation
I’ve created a helper method that returns an ImageField
, which then I use at the object’s creation with the model’s manager:
def get_test_image_file():
from django.core.files.images import ImageFile
file = tempfile.NamedTemporaryFile(suffix='.png')
return ImageFile(file, name=file.name)
The tempfile Python’s module have some really handy functions, you should take a look.
MEDIA_ROOT = tempfile.mkdtemp()
@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class MeuPetTest(TestCase):
@classmethod
def tearDownClass(cls):
shutil.rmtree(MEDIA_ROOT, ignore_errors=True)
super().tearDownClass()
def create_pet(self):
return Pet.objects.create(profile_picture=get_test_image_file())
There’s four comments I’d like to make about this code:
- It creates a directory at my
/tmp/
and set it’s name to theMEDIA_ROOT
variable. - Using override_settings I change the
MEDIA_ROOT
setting to this temporary directory. - At the
tearDownClass
I delete this directory. - When I create an object of the given model, the image field get the
ImageFile
returned byget_test_image_file
.
This way, even if some file was left without being deleted, at my computer’s reboot the system will erase it by itself, saving me from some manual file deletion.
Testing file upload with post
For tests with the POST
requests my approach needs to change a bit, as I’ve declared the profile image field as an ImageField
we can see in the source code of Django it makes a few assumptions about the data that will be passed to this field.
To satisfy Django’s validations the solution that best worked for me was this:
@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class PetRegisterTest(TestCase):
def _create_image(self):
from PIL import Image
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
image = Image.new('RGB', (200, 200), 'white')
image.save(f, 'PNG')
return open(f.name, mode='rb')
def setUp(self):
self.image = self._create_image()
def tearDown(self):
self.image.close()
def test_show_registered_page(self):
response = self.client.post(reverse('pet:register'),
data={'profile_picture': self.image},
follow=True)
A few observations about this code:
- It uses the override_settings just like the other one.
- It also uses
PIL
to create a valid image and adds it to a temporary file. - At the context manager when I create the temporary file I add an extra argument
delete=False
. I did it this way because NamedTemporaryFile by default deletes the file when it’s closed, and we don’t want it. - An important thing to note here is the way I open the file for reading before returning it, we need to guarantee that
read
returns bytes.
This two ways worked really well for me and I hope it also help others people. ;)