Chapter 11: Testing Streamlit apps
This chapter introduces a testing strategy for Streamlit apps, covering automated tests at multiple levels to catch defects and prevent regressions as an app evolves.
After this chapter you will be able to
- Testing strategy for Streamlit apps. Explain why a Streamlit app needs a testing strategy that verifies its frontend, backend, and external dependencies under different conditions. Describe how testing catches bugs before users encounter them and protects existing features when code changes.
- Test-level distinctions. Distinguish unit, integration, and end-to-end tests by their scope: individual components, connections between components, and the complete application flow. State that these test types each contribute a different piece of a broader testing strategy.
- MovieScout search architecture. Describe the MovieScout request and response flow: a search term from the Streamlit frontend goes to the backend, which calls an external movie-search API, parses its response, and returns convenient movie results for the frontend to render.
- TMDB API purpose. Explain why MovieScout delegates movie searching and movie-information retrieval to the TMDB API, and name the kinds of movie data TMDB can provide.
- Movie search API request. Describe how to query TMDB's movie-search endpoint with requests by sending an HTTP GET request to https://api.themoviedb.org/3/search/movie and passing a params dictionary containing the API key and a search term. Explain that response.json() produces a dictionary whose results key contains a list of movies with fields such as ID and overview.
- Backend class responsibilities. Describe the two responsibilities of the Backend class: searching the TMDB API for movies and constructing full URLs for movie poster images.
- Movie result dictionary. Explain that search_movies accepts a search query, queries the TMDB API, and converts the API response's results list into a dictionary keyed by movie ID.
- MovieScout search flow. Describe how the frontend presents a sidebar text box and Search button, then calls backend.py's search_movies method and stores the returned results in st.session_state.
- Movie result presentation. Explain how MovieScout renders available results by looping through them and creating an st.expander labeled with each movie's title and release year, then showing its poster, overview, and rating.
- Testing reliability purpose. Explain why thorough testing is essential for a Streamlit application, including checking expected behavior under unexpected inputs and as components change over time.
- Test category strategy. Describe the section’s testing strategy as using different categories of tests, with each test type serving a distinct purpose within an overall approach for MovieScout.
- Manual and automated testing. Distinguish manual testing from automated testing, including how each verifies app behavior and the practical drawbacks of manual testing.
- Automated test categories. Describe the scope of unit, integration, and end-to-end tests: isolated functions or methods, interactions among components, and the complete application workflow under real-world conditions.
- Bottom-up testing sequence. Describe the bottom-up testing approach for MovieScout, tracing the progression from backend unit tests through frontend unit tests and integration tests to an end-to-end test. Explain that smaller components are verified before they are combined into larger tests.
- Test isolation and live dependencies. Distinguish which MovieScout tests isolate or simulate dependencies and which tests use the real TMDB API. Explain that backend unit tests mock the external API, frontend unit tests use AppTest to simulate user actions without a real backend, integration testing includes a test that calls TMDB, and the final end-to-end test uses live API data without mocks.
- Minimal pytest test workflow. Describe the minimal pytest workflow: put a function and a test such as test_add in a test file, call the function with inputs, use Python's assert to check the expected result, and run pytest -v from that folder.
- Unit test isolation. Explain that backend unit tests test individual code components in isolation rather than testing the whole application flow together.
- Unit-test isolation. Explain why Backend.search_movies must be tested independently of the requests module and the TMDB API. State that a unit test evaluates the application's own component rather than behavior controlled by external systems.
- Dependency mocking. Describe mocking as replacing real external dependencies with fake, controlled versions during a test. Explain that mocks can simulate API responses and prevent network behavior, unreliable services, slow calls, or real API calls from affecting unit-test results.
- Unit-test phases. Trace the setup, testing, and teardown structure of a unit test. Describe that the testing phase establishes mocked conditions and inputs, defines expected output, runs the method, and uses assertions to compare actual and expected results, passing only when all assertions are true.
- Reusable backend fixture. Explain how the pytest backend fixture constructs and returns a Backend instance with a dummy API key, and how pytest injects that returned instance into a test that declares backend as a parameter.
- Patched search request test. Trace the successful search_movies unit test: @patch('requests.get') temporarily substitutes a mock HTTP function, the test configures a fake response whose json method returns mock data, calls search_movies with a dummy query, and asserts both the request and transformed results.
- Pytest test execution. Describe how to run the Backend unit tests by opening a terminal in the directory containing test_backend.py and running pytest -v. State that the command reports whether the tests pass or fail.
- Failure diff interpretation. Describe how pytest reports the intentionally failed test_search_movies_no_results test by showing a diff between the expected return value and the value actually returned. State that the temporary faulty change should be undone before continuing to frontend testing.
- Frontend testing foundations. Explain which unit-testing practices remain the same for a Streamlit frontend: setting up before each test, tearing down afterward, and isolating the component from external dependencies through patching or mocking.
- Interactive frontend behavior. Describe why frontend testing needs simulated user interaction: unlike backend execution, whose behavior is defined by supplied input values, frontend behavior also depends on actions such as clicking buttons and entering text.
- AppTest capabilities. Describe AppTest as Streamlit's class for simulating a running app from the app's code, mimicking user interactions, and inspecting rendered output.
- Mocked backend isolation. Explain why the Backend class is mocked when unit testing the frontend, and describe how MockBackend replaces its methods with controlled mock values such as MOCK_RESULTS and a dummy poster URL.
- AppTest fixture setup. Describe how the setup_app pytest fixture creates an AppTest from frontend.py, inserts a test API key into the app’s secrets, and returns a runnable app for tests that request the fixture.
- Initial UI state assertions. Describe how the initial-state test runs the AppTest and uses AppTest element collections and session state to assert the sidebar’s expected controls and labels, while confirming that no search results or result expanders appear yet.
- Simulated search interactions. Trace how a frontend test enters text into the search input, clicks the Search button, and calls run after each action to trigger the reruns that real Streamlit user actions would cause; then describe assertions for stored mock results and displayed expanders.
- Silent rating-key bug. Trace the failed rating assertion to the frontend code using a nonexistent rating dictionary key rather than vote_average, and explain why the default N/A value prevents Python from raising an exception.
- Unit-test scope limitation. Distinguish passing isolated frontend and backend unit tests from evidence that the two components work together, and state why integration tests are needed next.
- Need for integration testing. Explain why components that pass tests in isolation can still fail when combined, and why integration tests verify interactions among the parts of an application.
- Integration boundaries. Describe the app as a chain of frontend, backend, and external API components, and distinguish the two pairwise integration boundaries tested in this section.
- Frontend-backend integration test. Explain how the frontend-backend integration test reuses the frontend test setup and simulated interactions while patching only requests.get, so the real frontend and Backend class run together without calling the external API.
- Backend-API integration test. Describe how test_backend_api_integration instantiates the Backend class directly and calls the real TMDB API using the API key from secrets.toml, then asserts that a likely search returns at least one result including Ferris Bueller’s Day Off.
- End-to-end testing purpose. Explain how end-to-end tests differ from unit and integration tests in this chapter: they check whether the complete application behaves correctly under real-world conditions rather than isolating components from dependencies.
- MovieScout end-to-end test. Describe how to create and run MovieScout's end-to-end test: put it in test_e2e.py, create a running app with AppTest without patching Backend or supplying mock data, search for the real movie Inception, and assert that results and their Streamlit expanders appear.
- Code and test coevolution. Explain why application code and test code must evolve together after a change. Distinguish a failing test caused by a regression from one caused by an outdated test, and state when new tests are needed.
- Automated test enforcement. Describe ways to automate test runs with Git hooks or continuous integration. State that a pre-commit hook can run non-API tests before a commit and prevent the commit when they fail, while larger projects can use CI such as GitHub Actions on code pushes.
What it builds on
- Building your first app (chapter 2) · The basic structure of a Streamlit app with user input, logic, and displayed output.
- Deploying an app that uses an external service (chapter 5) · Calling an external API from a Streamlit app using Python.
- Using the requests library to connect to an API (chapter 5) · Using the requests library to connect to an API.
- st.session_state (chapter 4) · The use of st.session_state to persist values across Streamlit reruns.
- Virtual environments (chapter 2) · Using a Python environment in which packages can be installed.
- App reruns (chapter 4) · Streamlit’s rerun behavior after user interactions.
- Accessing an API key safely with st.secrets (chapter 5) · Using secrets.toml to access an API key safely.
- Version control with Git (chapter 2) · Using Git for version control
Section by section
Introduction
This chapter introduces a testing strategy for Streamlit apps, covering automated tests at multiple levels to catch defects and prevent regressions as an app evolves.
- Testing strategy for Streamlit apps: Explain why a Streamlit app needs a testing strategy that verifies its frontend, backend, and external dependencies under different conditions. Describe how testing catches bugs before users encounter them and protects existing features when code changes.
Why it matters: A testing strategy helps keep a polished app from failing when users, APIs, or later updates behave unexpectedly. - Test-level distinctions: Distinguish unit, integration, and end-to-end tests by their scope: individual components, connections between components, and the complete application flow. State that these test types each contribute a different piece of a broader testing strategy.
Why it matters: Using several test levels provides evidence both that individual pieces work and that the whole app works together. - Automated testing toolchain: Describe the chapter's planned use of pytest to write and run automated tests and Streamlit's AppTest to create a live app with simulated user interactions. Explain that these tools support validation of app behavior rather than relying only on manual checking.
Why it matters: Automated, repeatable checks make it practical to validate behavior whenever the application changes. - Test evolution with code: State that tests should evolve alongside application code so that changes can be validated without inadvertently breaking existing functionality.
Why it matters: Keeping tests current lets them continue to detect regressions as an application grows and changes.
Key terms: testing strategy, frontend, backend, external dependency, existing features, unit tests, integration tests, end-to-end tests, pytest, automated tests
11.1 MovieScout: An app to test
This section introduces MovieScout as a deliberately simple movie-search app and outlines its frontend-to-backend-to-API data flow as the subject for the chapter's tests.
- MovieScout testing purpose: Explain why MovieScout is used in this chapter: it is simple enough to keep attention on testing, yet complex enough that testing it teaches meaningful lessons.
Why it matters: MovieScout provides a focused example through which the chapter demonstrates how to test a Streamlit application. - MovieScout search architecture: Describe the MovieScout request and response flow: a search term from the Streamlit frontend goes to the backend, which calls an external movie-search API, parses its response, and returns convenient movie results for the frontend to render.
Why it matters: This component boundary and data flow identify the backend, frontend, and integration points that the later tests examine.
Key terms: MovieScout, Streamlit frontend, backend, external movie search API, API response object, search results
11.1.1 Using the TMDB API to pull movie info
This section introduces TMDB as the third-party movie-data API for MovieScout and shows how to obtain a key and make a basic movie search request.
- TMDB API purpose: Explain why MovieScout delegates movie searching and movie-information retrieval to the TMDB API, and name the kinds of movie data TMDB can provide.
Why it matters: Using a third-party API keeps the app's own movie-search logic simple while providing rich movie data. - TMDB API key acquisition: Describe how to obtain a TMDB API key: create and log into a TMDB account, register on its API page, choose Personal as the type of use, and use the API Key rather than the API Read Access Token.
Why it matters: The API key identifies the request to TMDB and is needed before the app can retrieve movie data. - Movie search API request: Describe how to query TMDB's movie-search endpoint with requests by sending an HTTP GET request to https://api.themoviedb.org/3/search/movie and passing a params dictionary containing the API key and a search term. Explain that response.json() produces a dictionary whose results key contains a list of movies with fields such as ID and overview.
Why it matters: This request-and-response pattern is the concrete way MovieScout obtains searchable movie records from TMDB.
Key terms: TMDB, The Movie Database, TMDB API, third-party API, TMDB account, API registration, Personal, API Key, API Read Access Token, requests
11.1.2 The backend
This subsection implements a Backend class that searches TMDB movies and turns poster paths into complete image URLs.
- Backend class responsibilities: Describe the two responsibilities of the Backend class: searching the TMDB API for movies and constructing full URLs for movie poster images.
Why it matters: Separating these API and data-formatting tasks into a backend makes the frontend simpler to build and test. - Movie result dictionary: Explain that search_movies accepts a search query, queries the TMDB API, and converts the API response's results list into a dictionary keyed by movie ID.
Why it matters: Keying returned movies by ID gives the application a structured way to identify each search result. - Poster path URL construction: Describe how the static get_poster_url method takes a poster_path from the API response and combines it with the TMDB image base URL and w500 size path to produce a full poster-image URL.
Why it matters: The API provides a poster path rather than a directly usable image address, so the app must construct the complete URL.
Key terms: Backend class, search_movies, get_poster_url, TMDB API, search query, results list, dictionary, movie ID, API response, static method
11.1.3 The frontend
This section builds MovieScout’s simple Streamlit frontend, from securing its API key to searching for and displaying movie results.
- MovieScout search flow: Describe how the frontend presents a sidebar text box and Search button, then calls backend.py's search_movies method and stores the returned results in st.session_state.
Why it matters: This flow connects the user’s search action to persistent results that the rest of the interface can render. - Movie result presentation: Explain how MovieScout renders available results by looping through them and creating an st.expander labeled with each movie's title and release year, then showing its poster, overview, and rating.
Why it matters: Organizing each result into an expandable movie card makes a potentially long result list readable while retaining useful details. - Image rendering options: Describe how st.image renders a movie poster from its URL at a fixed width of 250 pixels, and state that it can optionally add a caption or render a row of images when given a list of URLs.
Why it matters: Knowing these st.image options lets the frontend turn poster URLs into appropriately formatted visual content.
Key terms: Search button, search_movies, st.session_state, results, backend.py, st.expander, get_poster_url, st.markdown, overview, rating
11.2 Testing the application
This section establishes the testing foundation for MovieScout by introducing test categories, the project’s testing strategy, and pytest.
- Testing reliability purpose: Explain why thorough testing is essential for a Streamlit application, including checking expected behavior under unexpected inputs and as components change over time.
Why it matters: Testing helps keep an application reliable beyond the ideal cases demonstrated during development. - Test category strategy: Describe the section’s testing strategy as using different categories of tests, with each test type serving a distinct purpose within an overall approach for MovieScout.
Why it matters: A deliberate mix of test types provides broader confidence than relying on just one kind of test. - Pytest test framework: State that pytest is the Python testing framework introduced to power the MovieScout test suite.
Why it matters: Pytest is the tool used in the following sections to write and run the application’s tests.
Key terms: testing, reliable software development, unexpected inputs, components change over time, categories of tests, test approach, MovieScout, overall strategy, pytest, Python testing framework
11.2.1 Different kinds of tests
This section distinguishes manual from automated testing and defines the unit, integration, and end-to-end automated tests used in the chapter.
- Manual and automated testing: Distinguish manual testing from automated testing, including how each verifies app behavior and the practical drawbacks of manual testing.
Why it matters: Choosing automated checks helps catch problems consistently without repeatedly relying on a person to exercise the app. - Automated test categories: Describe the scope of unit, integration, and end-to-end tests: isolated functions or methods, interactions among components, and the complete application workflow under real-world conditions.
Why it matters: These categories let you test an app at progressively broader levels, from a single piece of code to the user's full workflow. - Chapter testing focus: State that the chapter focuses on unit, integration, and end-to-end tests because together they provide a foundation for catching errors early and maintaining code quality.
Why it matters: The three automated test levels are presented as a practical baseline for finding defects early while keeping the code dependable.
Key terms: manual testing, automated testing, unit tests, integration tests, end-to-end (E2E) tests, frontend, backend, code quality
11.2.2 Our testing approach
This section outlines MovieScout’s bottom-up testing strategy, progressing from isolated unit tests to a live end-to-end test.
- Bottom-up testing sequence: Describe the bottom-up testing approach for MovieScout, tracing the progression from backend unit tests through frontend unit tests and integration tests to an end-to-end test. Explain that smaller components are verified before they are combined into larger tests.
Why it matters: A staged testing strategy helps locate problems in individual components before they become harder to diagnose in the complete app. - Test isolation and live dependencies: Distinguish which MovieScout tests isolate or simulate dependencies and which tests use the real TMDB API. Explain that backend unit tests mock the external API, frontend unit tests use AppTest to simulate user actions without a real backend, integration testing includes a test that calls TMDB, and the final end-to-end test uses live API data without mocks.
Why it matters: Knowing when dependencies are mocked, simulated, or live lets you choose a test setup that checks the intended behavior.
Key terms: bottom-up testing approach, unit tests, integration tests, end-to-end test, Backend class, TMDB API, external dependency, mocked, AppTest, frontend
11.2.3 A quick introduction to pytest
This section introduces pytest by showing how to define, run, and interpret a minimal automated Python test.
- Pytest purpose and features: Explain that pytest is the Python testing framework used throughout the chapter to write and execute automated tests, and describe its concise syntax, automatic test discovery, and debugging information.
Why it matters: Knowing what pytest provides makes the later testing examples easier to understand and run. - Minimal pytest test workflow: Describe the minimal pytest workflow: put a function and a test such as test_add in a test file, call the function with inputs, use Python's assert to check the expected result, and run pytest -v from that folder.
Why it matters: This is the basic pattern used to turn expected program behavior into repeatable checks.
Key terms: pytest, automated tests, test discovery, test_example.py, add, test_add, assert, pytest -v
11.3 Unit testing the backend
This section introduces unit testing the MovieScout backend, whose job is to query TMDB and format search results for the frontend.
- Backend responsibilities: Describe the Backend class’s role in communicating with the TMDB API, fetching results for a search query, and converting those results into the dictionary format returned to the frontend.
Why it matters: Knowing the backend’s responsibilities identifies the behavior its unit tests need to check. - Unit test isolation: Explain that backend unit tests test individual code components in isolation rather than testing the whole application flow together.
Why it matters: Testing isolated components helps locate whether a failure belongs to a specific piece of backend code.
Key terms: Backend class, TMDB API, search query, dictionary format, frontend, unit testing, individual components, isolation, backend
11.3.1 How unit testing works
This section explains how unit tests isolate a single component from external dependencies by using mocks and checking expected behavior through assertions.
- Unit-test isolation: Explain why Backend.search_movies must be tested independently of the requests module and the TMDB API. State that a unit test evaluates the application's own component rather than behavior controlled by external systems.
Why it matters: Isolation ensures that a failure in a third-party service does not falsely report a defect in your code. - Dependency mocking: Describe mocking as replacing real external dependencies with fake, controlled versions during a test. Explain that mocks can simulate API responses and prevent network behavior, unreliable services, slow calls, or real API calls from affecting unit-test results.
Why it matters: Mocks make tests controlled and repeatable even when the network and external services are not. - Unit-test phases: Trace the setup, testing, and teardown structure of a unit test. Describe that the testing phase establishes mocked conditions and inputs, defines expected output, runs the method, and uses assertions to compare actual and expected results, passing only when all assertions are true.
Why it matters: This structure gives each test a clear, repeatable way to demonstrate one expected behavior.
Key terms: unit testing, Backend class, search_movies, external dependencies, requests module, TMDB API, mocking, mocks, API responses, setup phase
11.3.2 Writing unit tests for the backend
This section builds pytest unit tests for the MovieScout backend by using mock movie data, fixtures, patched HTTP calls, and assertions for normal and edge-case behavior.
- Mock movie-data structures: Describe the mock data created in mock_data.py: two API-shaped movie dictionaries, a MOCK_MOVIES list standing in for API results, and a MOCK_RESULTS dictionary keyed by movie ID. Explain that the mock records include only fields the app uses.
Why it matters: Well-shaped mock data lets tests simulate API responses without relying on the real TMDB service. - Reusable backend fixture: Explain how the pytest backend fixture constructs and returns a Backend instance with a dummy API key, and how pytest injects that returned instance into a test that declares backend as a parameter.
Why it matters: Fixtures remove repeated setup and give every backend test a consistent, isolated object to exercise. - Patched search request test: Trace the successful search_movies unit test: @patch('requests.get') temporarily substitutes a mock HTTP function, the test configures a fake response whose json method returns mock data, calls search_movies with a dummy query, and asserts both the request and transformed results.
Why it matters: Patching the external request makes the test deterministic while still verifying that search_movies sends the right request and processes its response correctly. - Backend edge-case tests: Describe the additional backend tests: search_movies returns an empty dictionary for an empty mocked result set, and the static Backend.get_poster_url method is tested directly for both a defined poster path and None without a patch or Backend fixture.
Why it matters: Testing empty results and missing poster paths verifies that backend helpers behave predictably beyond the happy path.
Key terms: MOCK_MOVIE_1, MOCK_MOVIE_2, MOCK_MOVIES, MOCK_RESULTS, mock_data.py, pytest.fixture, fixture, Backend, dummy API key, @patch('requests.get')
11.3.3 Running the tests
This section shows how to run Backend unit tests with pytest, interpret a passing or failing result, and undo a deliberately introduced faulty expectation.
- Pytest test execution: Describe how to run the Backend unit tests by opening a terminal in the directory containing test_backend.py and running pytest -v. State that the command reports whether the tests pass or fail.
Why it matters: Running the suite verifies that the Backend code still behaves as its tests require. - Verbose pytest output: Explain that the -v option means verbose and causes pytest to provide more information about the tests than it normally would.
Why it matters: Verbose output gives clearer feedback while checking test outcomes and diagnosing failures. - Failure diff interpretation: Describe how pytest reports the intentionally failed test_search_movies_no_results test by showing a diff between the expected return value and the value actually returned. State that the temporary faulty change should be undone before continuing to frontend testing.
Why it matters: Reading pytest's expected-versus-actual diff helps locate why a test failed and prevents an intentional test error from remaining in the suite.
Key terms: pytest, test_backend.py, pytest -v, -v, verbose, test_search_movies_no_results, diff, expected return value, actual return value, []
11.4 Unit testing the Streamlit frontend
This section introduces Streamlit frontend unit testing as ordinary isolated unit testing plus simulated user interaction and output inspection through AppTest.
- Frontend testing foundations: Explain which unit-testing practices remain the same for a Streamlit frontend: setting up before each test, tearing down afterward, and isolating the component from external dependencies through patching or mocking.
Why it matters: Frontend tests are reliable only when they control their environment and dependencies just like backend tests do. - Interactive frontend behavior: Describe why frontend testing needs simulated user interaction: unlike backend execution, whose behavior is defined by supplied input values, frontend behavior also depends on actions such as clicking buttons and entering text.
Why it matters: A frontend test must reproduce relevant user actions in order to verify what the app does in response. - AppTest capabilities: Describe AppTest as Streamlit's class for simulating a running app from the app's code, mimicking user interactions, and inspecting rendered output.
Why it matters: AppTest provides the mechanism for testing a Streamlit interface programmatically rather than manually.
Key terms: unit testing, testing environment, tear down, external dependencies, patching, mocking, frontend, backend, user interaction, input values
11.4.1 Writing unit tests for the frontend
This section shows how to unit test MovieScout’s Streamlit frontend in isolation by mocking its backend, setting up an AppTest fixture, and asserting both initial UI state and simulated user interactions.
- Mocked backend isolation: Explain why the Backend class is mocked when unit testing the frontend, and describe how MockBackend replaces its methods with controlled mock values such as MOCK_RESULTS and a dummy poster URL.
Why it matters: Mocking the backend ensures a frontend test fails only for frontend problems, not because an API or backend dependency failed. - AppTest fixture setup: Describe how the setup_app pytest fixture creates an AppTest from frontend.py, inserts a test API key into the app’s secrets, and returns a runnable app for tests that request the fixture.
Why it matters: A reusable fixture gives each frontend test a prepared app without requiring a real secrets.toml file or manually repeating setup. - Initial UI state assertions: Describe how the initial-state test runs the AppTest and uses AppTest element collections and session state to assert the sidebar’s expected controls and labels, while confirming that no search results or result expanders appear yet.
Why it matters: Testing the initial state catches missing, mislabeled, or prematurely displayed interface elements before a user interacts with the app. - Simulated search interactions: Trace how a frontend test enters text into the search input, clicks the Search button, and calls run after each action to trigger the reruns that real Streamlit user actions would cause; then describe assertions for stored mock results and displayed expanders.
Why it matters: Simulating interactions and reruns lets a unit test verify the user-visible search flow without opening a browser or calling the real backend. - Result display content checks: Describe how a test can preload session_state["results"] with MOCK_RESULTS before the first run to bypass the button-click path, then verify both the number of expanders and movie overview text in rendered markdown elements.
Why it matters: Checking rendered content, not only element counts, verifies that the frontend presents the correct movie information.
Key terms: Backend, MockBackend, MOCK_RESULTS, mock_data.py, unit testing, mock, pytest.fixture, setup_app, AppTest, AppTest.from_file
11.4.2 Running frontend tests
This section runs the frontend unit tests, uses their failure to uncover and fix a silent movie-rating display bug, and motivates integration testing next.
- Frontend test execution: Describe how to run the frontend unit tests with pytest -v from the directory that contains them, and explain what the successful rerun establishes about the frontend in isolation.
Why it matters: Running tests after a change verifies that the specific frontend behavior under test now works. - Silent rating-key bug: Trace the failed rating assertion to the frontend code using a nonexistent rating dictionary key rather than vote_average, and explain why the default N/A value prevents Python from raising an exception.
Why it matters: Tests can expose incorrect output that a fallback value otherwise hides without crashing the app. - Unit-test scope limitation: Distinguish passing isolated frontend and backend unit tests from evidence that the two components work together, and state why integration tests are needed next.
Why it matters: Separate component tests cannot by themselves detect failures in the connection between components.
Key terms: pytest, pytest -v, frontend unit tests, rating_text, vote_average, rating, N/A, markdown elements, unit tests, frontend
11.5 Testing the integration between components
This section explains why integration tests are needed to verify that app components and external dependencies work correctly together, beyond isolated unit tests.
- Need for integration testing: Explain why components that pass tests in isolation can still fail when combined, and why integration tests verify interactions among the parts of an application.
Why it matters: Integration testing finds failures at component boundaries that isolated tests cannot reveal. - External dependency interactions: Describe why external dependencies remain part of application correctness even when tests isolate code from them, using the TMDB API returning incorrect movie results as the example.
Why it matters: An app is not functioning as intended if a critical service it depends on fails to provide the expected results.
Key terms: integration test, unit test, components, external dependencies, TMDB API
11.5.1 Writing integration tests
This section writes two integration tests for MovieScout: one for the frontend and backend together with the API isolated, and one for the backend against the real TMDB API without the frontend.
- Integration boundaries: Describe the app as a chain of frontend, backend, and external API components, and distinguish the two pairwise integration boundaries tested in this section.
Why it matters: Identifying which components are included and isolated lets you test their collaboration without testing everything at once. - Frontend-backend integration test: Explain how the frontend-backend integration test reuses the frontend test setup and simulated interactions while patching only requests.get, so the real frontend and Backend class run together without calling the external API.
Why it matters: This test verifies that user actions can flow correctly from the Streamlit interface through the backend while keeping API responses controlled. - Backend-API integration test: Describe how test_backend_api_integration instantiates the Backend class directly and calls the real TMDB API using the API key from secrets.toml, then asserts that a likely search returns at least one result including Ferris Bueller’s Day Off.
Why it matters: Testing against the live API can reveal whether the backend still works with the external service it depends on.
Key terms: frontend, backend, external API, integration tests, setup_app fixture, Backend class, requests.get, mocking, patching, test_backend_api_integration
11.6 Testing the end-to-end flow
This section shows how an end-to-end test runs MovieScout as a whole against the real TMDB API and verifies that a real movie search produces displayed results.
- End-to-end testing purpose: Explain how end-to-end tests differ from unit and integration tests in this chapter: they check whether the complete application behaves correctly under real-world conditions rather than isolating components from dependencies.
Why it matters: End-to-end testing gives confidence that the whole app works together, including its real external services. - MovieScout end-to-end test: Describe how to create and run MovieScout's end-to-end test: put it in test_e2e.py, create a running app with AppTest without patching Backend or supplying mock data, search for the real movie Inception, and assert that results and their Streamlit expanders appear.
Why it matters: This provides a simple practical pattern for verifying that a Streamlit app, its frontend, backend, and live API work together.
Key terms: end-to-end tests, unit tests, integration tests, real-world conditions, external dependencies, test_e2e.py, AppTest, Backend, mock data, TMDB API
11.7 Evolving code and tests
This section explains how tests should evolve with application code, be run selectively and repeatedly, and be automated to catch problems early.
- Code and test coevolution: Explain why application code and test code must evolve together after a change. Distinguish a failing test caused by a regression from one caused by an outdated test, and state when new tests are needed.
Why it matters: Treating tests as part of the application helps preserve correct behavior as the app changes. - Test execution frequency: Describe why unit tests should run frequently and why integration and end-to-end tests may be run less often. Explain that pytest can tag and filter tests so developers run only the tests needed.
Why it matters: Choosing an appropriate subset of tests gives quick feedback without unnecessary delays, external-service usage, or cost. - Automated test enforcement: Describe ways to automate test runs with Git hooks or continuous integration. State that a pre-commit hook can run non-API tests before a commit and prevent the commit when they fail, while larger projects can use CI such as GitHub Actions on code pushes.
Why it matters: Automated checks catch failures early without relying on someone remembering to run the test suite.
Key terms: automated tests, unit tests, integration tests, end-to-end tests, regression, application code, pytest, tag, filter, Git hooks
11.8 Summary
This summary recaps how to test Streamlit applications with pytest, mocks, AppTest, and automated test workflows.
Where this is used later
- Go forth and develop! (chapter 12) · Ongoing development after deployment relies on protecting working behavior as the app changes.
Concepts explained in this chapter
- testA test function is a callable test definition that exercises software and compares observed results with expected results.
- mockingMocking is the process of replacing real dependencies with fake, controlled versions during testing.
- assertionAn assertion is a test check that evaluates an expected condition and reports a failure when the condition is false.
- test fixtureA test fixture is a reusable setup and teardown mechanism that provides a controlled environment or data for tests.
- AppTestAppTest is Streamlit's testing API for creating and programmatically interacting with a runnable app instance without displaying its browser interface.
- pytestpytest is a Python testing framework for writing and running tests with concise test functions, assertions, fixtures, and plugins.
- integration testAn integration test evaluates whether multiple software components and their dependencies interact correctly as a combined system.
- end-to-end testingAn end-to-end test evaluates the behavior of an entire application under conditions that include its real dependencies and external services.
- patch decoratorunittest.mock.patch is a Python standard-library decorator and context manager that temporarily replaces an object with a substitute during a test.
- dependencyAn external dependency is software or a remote service outside a component's implementation that the component relies on for functionality.
- frontend testingFrontend testing is the practice of verifying the behavior and rendered interface of an application's user-facing part.
- sessionst.session_state is Streamlit's session-scoped state store for retaining values across reruns of an app session.
- software updatePatching is a testing technique that temporarily replaces a referenced object, function, or dependency with a controlled substitute during execution.
- mock dataMock data is fabricated data designed to imitate the structure and content of data supplied by a real dependency.
- deployment pipelineContinuous integration is the practice of automatically validating code changes as they are integrated into a shared codebase.
- precommit hookA pre-commit hook is an automated action triggered before a version-control commit is created.
- frontend developmentFrontend development is the software engineering discipline concerned with implementing the user-facing parts of web applications.
- API keyAn API key is a secret credential that authenticates software when it accesses an application programming interface.
- HTTP GETA GET request is an HTTP request that retrieves a resource or result, with request parameters commonly encoded in the URL.
- requestsrequests is a Python library that provides a user-friendly interface for sending HTTP requests and handling their responses.
- response.json()response.json is a requests response method that parses a JSON response body into a Python object.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
