streamlit / streamlit

Streamlit — A faster way to build and share data apps.
https://streamlit.io
Apache License 2.0
35.52k stars 3.08k forks source link

[AppTest] Add file_upload widget #8093

Open H4dr1en opened 9 months ago

H4dr1en commented 9 months ago

Checklist

Summary

Requesting support for the file_upload widget in AppTest

Why?

It blocks testing the rest of the app as its logic depends on the uploaded file

How?

Add support for the file_upload widget in AppTest

Additional Context

No response

github-actions[bot] commented 9 months ago

To help Streamlit prioritize this feature, react with a 👍 (thumbs up emoji) to the initial post.

Your vote helps us identify which enhancements matter most to our users.

Visits

H4dr1en commented 9 months ago

Also would be cool to if AppTest could support st.image, st.video, st.download_button

JuliaS92 commented 2 months ago

For anybody else looking for a simple workaround until the file_uploader is added to the supported widgets, here is a solution I now use:

App file:

import streamlit as st
import pandas as pd

st.title('Hello World')
file = st.file_uploader('Upload a file')

if file is not None:
    df = pd.read_csv(file)
    st.write(df)
else:
    st.write('Please upload a file')

Test file with mocking using the unittest.mock:

from streamlit.testing.v1 import AppTest
from unittest.mock import MagicMock, patch
import pandas as pd
from io import BytesIO

def test_loadpage():
    at = AppTest(f"app.py", default_timeout=100)
    at.run()

    assert at.markdown[0].value == 'Please upload a file'

@patch("streamlit.file_uploader")
def test_loadmockedpage(mock_file: MagicMock):
    mock_file.return_value = None

    at = AppTest(f"app.py", default_timeout=100)
    at.run()

    assert at.markdown[0].value == 'Please upload a file'

@patch("streamlit.file_uploader")
def test_mockupload(mock_file: MagicMock):
    expected_file = "pathto.csv"
    with open(expected_file, "rb") as f:
        mock_file.return_value = BytesIO(f.read())

    at = AppTest(f"app.py", default_timeout=100)
    at.run()

    expected_df = pd.read_csv(expected_file)
    assert at.dataframe[0].value.equals(expected_df)

Contrary to how mocks work normally (point to where the original is used) you have to patch the file_uploader in streamlit.

shamikbosefj commented 2 months ago

@JuliaS92 Is there a way to do the same functionality with pytest?

JuliaS92 commented 2 months ago

@shamikbosefj This works when running the tests with pytest. Pytest seems to have it's own module, that uses unittest under the hood if I get this right. So if you want to avoid unittest.mock that should be doable.

shamikbosefj commented 2 months ago

Thanks, @JuliaS92 ! I'll try that one out and update here if I can get it working

shamikbosefj commented 2 months ago

@JuliaS92 One quick question. I'm trying to upload an image file, which runs through some processing and updates the app. I can see that the file is being uploaded, but the app values don't change according to the test suite. Any ideas what I might be doing wrong?

#streamlit app
def process_image(uploaded_file):
  Do something with file
  update app

UPDATE: The mocked snippet above works great! I had to reinitialize the containers within the testing script after app.run() as follows

tab_1 = at.tabs[0]
at.run()
tab_1 = at.tabs[0]