Pointers and tips for testing `ImageField` and `FileField` incl. deletion?

Hello,

a lot of my models in my projects have important ImageFields (StdImageField in some cases) and FileFields.

I have already read a few blog posts and numerous SO posts but they mostly focus on mocking the files.

I would like to use real ones (even simple generated 1x1px images for example). Because I also added logic to clean-up these files when the objects are deleted and would like to test it also.

One of the things I tried most recently was the package for in-memory file storage. But this doesn’t support absolute paths so my file deleting logic doesn’t work with this.

Is there a simple way how to perhaps setup temporary media directory for the tests which will be removed similarly to testing database?

In summary I want to test:

  • Creating objects with images saved in ImageField
  • The same for FileField
  • Verify that the underlying files are also deleted

@adamchainz :pray: do you perhaps have some ideas? I found the in-memory storage in your Django book about testing.

The trick here is ContentFile: The File object | Django documentation | Django

book = Book.objects.create(cover=ContentFile(b"...", name="foo.png'))

For the bytes, I have often put the bytes of a single pixel PNG into my test file source, derived through open('foo.png').read() on the shell. This avoids an extra tiny file sitting around.

Thanks! Will try this.

Assuming my test fails, these files will linger around in the media root, right?

Right - you can write a unittest cleanup function that removes them:

book = Book.objects.create(cover=ContentFile(b"...", name="foo.png'))
self.addCleanup(partial(book.cover.delete, save=False))

I’ve also done this before in the model class, but I’ve not used it widely enough to be sure it’s always a good idea…

class Book(models.Model):
    cover = models.FileField(...)

    def delete(self, *args, **kwargs):
        super().delete(*args, **kwargs)

        @transaction.on_commit
        def remove_cover():
            self.cover.delete(save=False)

Tests would still need to ensure they call book.delete() - the normal test rollback won’t call delete() as it just undoes DB changes.

To be honest, thinking about all this again, it probably makes sense to use DB-based storage during tests. This should be fast and will be always rolled back.