Testing file uploads in Flask

前端 未结 4 406
庸人自扰
庸人自扰 2021-02-02 07:32

I\'m using Flask-Testing for my Flask integration tests. I\'ve got a form that has a file upload for a logo that I\'m trying to write tests for but I keep getting an error sayin

相关标签:
4条回答
  • 2021-02-02 07:56

    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)
    
    0 讨论(0)
  • 2021-02-02 08:04

    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)
    
    0 讨论(0)
  • 2021-02-02 08:07

    You can use Werkzeug's FileStorage (as used by Flask under the hood).

    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",
    )
    

    Notice that I use a real file on my server: tests/assets/my_video.mp4

    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
    
    0 讨论(0)
  • 2021-02-02 08:07

    While trying to find a bug in my code I've created a SSCCE for file upload (based on the docs) with a corresponding test based on other answers here. It might be useful for somebody:

    app.py:

    import base64
    import os
    import pathlib
    import tempfile
    import textwrap
    
    import flask
    import werkzeug.utils
    
    root = flask.Blueprint('root', __name__)
    
    @root.route('/', methods=['GET', 'POST'])
    def upload_file():
        if flask.request.method == 'POST':
            try:
                file = flask.request.files['file']
                if not file.filename:
                    raise LookupError()
                filename = werkzeug.utils.secure_filename(file.filename)
                file.save(pathlib.Path(flask.current_app.config['UPLOAD_FOLDER'], filename))
                flask.flash('File saved!', 'message')
            except LookupError:
                flask.flash('No file provided!', 'error')
            return flask.redirect(flask.url_for('root.upload_file'))
        else:
            return flask.render_template_string(textwrap.dedent(
                '''\
                <!doctype html>
                <title>Upload new File</title>
                {% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}
                <ul class=flashes>
                {% for category, message in messages %}<li class="{{ category }}">{{ message }}</li>
                {% endfor %}
                </ul>
                {% endif %}{% endwith %}
                <h1>Upload new File</h1>
                <form method=post enctype=multipart/form-data>
                <input type=file name=file>
                <input type=submit value=Upload>
                </form>
                '''
            ))
    
    def create_app():
        app = flask.Flask(__name__)
        app.config['UPLOAD_FOLDER'] = tempfile.gettempdir()
        app.secret_key = 'change-me'
        app.register_blueprint(root)
        return app
    
    if __name__ == '__main__':
        create_app()
    

    test_app.py:

    """upload tests"""
    
    import base64
    import io
    import unittest
    
    import werkzeug
    
    import app
    
    # https://raw.githubusercontent.com/mathiasbynens/small/master/jpeg.jpg
    SMALLEST_JPEG_B64 = """\
    /9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
    EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=
    """
    
    class BaseTestCase(unittest.TestCase):
        def test_save(self):
            with app.create_app().test_client() as client:
                file = werkzeug.datastructures.FileStorage(
                    stream=io.BytesIO(base64.b64decode(SMALLEST_JPEG_B64)),
                    filename="example image.jpg",
                    content_type="image/jpg",
                )
                response = client.post(
                    '/',
                    data=dict(
                        file=file,
                    ),
                    follow_redirects=True,
                    content_type='multipart/form-data',
                )
    
    0 讨论(0)
提交回复
热议问题