Testing file uploads in Flask

You can use Werkzeug's FileStorage (as used by Flask under the hood) which you don't need to install as it comes with Flask.

You can mock a file like this:

from werkzeug.datastructures import FileStorage
import io
import json

# Here we are mocking a JSON file called Input.json
my_dict = {"msg": "hello!"}
input_json = json.dumps(my_dict, indent=4).encode("utf-8")

mock_file = FileStorage(
    stream=io.BytesIO(input_json),
    filename="Input.json",
    content_type="application/json",
)

This example uses a real file to test against:

from werkzeug.datastructures import FileStorage


my_video = os.path.join("tests/assets/my_video.mp4")

my_file = FileStorage(
    stream=open(my_video, "rb"),
    filename="my_video.mp4",
    content_type="video/mpeg",
),

rv = client.post(
   "/api/v1/video",
   data={
      "my_video": my_file,
   },
   content_type="multipart/form-data"
)

Test to see it returns a response status code of 200:

assert "200" in rv.status

I can then test that the file arrives in a test directory on the server:

assert "my_video.mp4" in os.listdir("tests/my_test_path")

Also note, you need to set the mocked file to None on teardown otherwise you'll get a ValueError: I/O operation on closed file. . Below is a Pytest example:

    def setup_method(self):
        self.mock_file = FileStorage(
            stream=io.BytesIO(input_json),
            filename="Input.json",
            content_type="application/json",
        )

    def teardown_method(self):
        self.mock_file = None

The issue ended up not being that when one adds content_type='multipart/form-data' to the post method it expect all values in data to either be files or strings. There were integers in my data dict which I realised thanks to this comment.

So the end solution ended up looking like this:

def test_edit_logo(self):
    """Test can upload logo."""
    data = {'name': 'this is a name', 'age': 12}
    data = {key: str(value) for key, value in data.items()}
    data['file'] = (io.BytesIO(b"abcdef"), 'test.jpg')
    self.login()
    response = self.client.post(
        url_for('adverts.save'), data=data, follow_redirects=True,
        content_type='multipart/form-data'
    )
    self.assertIn(b'Your item has been saved.', response.data)
    advert = Item.query.get(1)
    self.assertIsNotNone(item.logo)

You need two things:

1.) content_type='multipart/form-data' in your .post()
2.) in your data= pass in file=(BytesIO(b'my file contents'), "file_name.jpg")

A full example:

    data = dict(
        file=(BytesIO(b'my file contents'), "work_order.123"),
    )

    response = app.post(url_for('items.save'), content_type='multipart/form-data', data=data)

Tags:

Python

Flask