Skip to main content
Flask provides testing utilities to help you test your application. These tools allow you to make requests to your application and inspect responses without running a server.

FlaskClient

A test client for making requests to your Flask application. Works like a regular Werkzeug test client, with additional behavior for Flask.
app = Flask(__name__)
app.testing = True

with app.test_client() as client:
    response = client.get("/")
    assert response.status_code == 200

Creating a Test Client

Use app.test_client() to create a test client:
def test_client(
    use_cookies: bool = True,
    **kwargs: Any
) -> FlaskClient
use_cookies
bool
default:"True"
Whether the client should handle cookies. Set to False to disable cookie handling.
kwargs
Any
Additional keyword arguments passed to the FlaskClient constructor. Useful when using a custom test client class.
Returns: A FlaskClient instance.

Making Requests

open

def open(
    *args: Any,
    buffered: bool = False,
    follow_redirects: bool = False,
    **kwargs: Any,
) -> TestResponse
Make a request to the application. This is the main method used by all HTTP method helpers.
args
Any
Can be a URL path, an EnvironBuilder, a WSGI environment dict, or a Request object.
buffered
bool
default:"False"
Whether to buffer the response data.
follow_redirects
bool
default:"False"
Whether to follow HTTP redirects automatically.
kwargs
Any
Additional arguments to build the request:
  • method: HTTP method (GET, POST, etc.)
  • data: Request body data
  • query_string: URL query parameters
  • headers: Request headers
  • json: JSON data (sets content-type automatically)
  • content_type: Content-Type header
Returns: A TestResponse object.

HTTP Method Helpers

Convenience methods for common HTTP methods:
response = client.get("/")
response = client.post("/login", data={"username": "test"})
response = client.put("/api/users/1", json={"name": "Updated"})
response = client.delete("/api/users/1")
response = client.patch("/api/users/1", json={"name": "Patched"})
All methods support the same parameters as open().

Session Management

session_transaction

@contextmanager
def session_transaction(
    *args: Any,
    **kwargs: Any
) -> Iterator[SessionMixin]
Modify the session in a test context. Changes are saved when the context exits.
with client.session_transaction() as session:
    session["user_id"] = 1

# Session is now saved, subsequent requests will see user_id
response = client.get("/profile")
args
Any
Arguments passed to test_request_context().
kwargs
Any
Keyword arguments passed to test_request_context().
Returns: A context manager that yields the session object. Raises: TypeError if cookies are disabled.

Context Preservation

Use the client as a context manager to preserve the request context:
with app.test_client() as client:
    response = client.get("/")
    # Context is preserved, can access request, session, g, etc.
    assert request.path == "/"

Attributes

environ_base
dict
Default environment variables for all requests. Set after creating the client:
client = app.test_client()
client.environ_base["REMOTE_ADDR"] = "192.168.1.1"
Default values:
  • REMOTE_ADDR: "127.0.0.1"
  • HTTP_USER_AGENT: "Werkzeug/{version}"
preserve_context
bool
Whether to preserve the request context. Automatically set to True when using the client as a context manager.

Version History

  • Changed in version 0.12: app.test_client() includes preset default environment, which can be set after instantiation in client.environ_base.
  • Changed in version 0.4: Added support for with block usage.

FlaskCliRunner

A CLI runner for testing Flask CLI commands. Based on Click’s CliRunner.
runner = app.test_cli_runner()
result = runner.invoke(args=["routes"])
assert result.exit_code == 0

Creating a CLI Runner

Use app.test_cli_runner() to create a CLI runner:
def test_cli_runner(**kwargs: Any) -> FlaskCliRunner
kwargs
Any
Keyword arguments passed to Click’s CliRunner constructor.
Returns: A FlaskCliRunner instance.

Running Commands

invoke

def invoke(
    cli: Any = None,
    args: Any = None,
    **kwargs: Any
) -> Result
Invoke a CLI command in an isolated environment.
cli
Any
default:"None"
Command object to invoke. Defaults to the app’s cli group.
args
list[str] | str
default:"None"
List of strings to invoke the command with, or a single string that will be split.
# List format
result = runner.invoke(args=["routes", "--sort", "endpoint"])

# String format  
result = runner.invoke(args="routes --sort endpoint")
kwargs
Any
Additional keyword arguments:
  • input: Input string for interactive commands
  • env: Environment variables dict
  • catch_exceptions: Whether to catch exceptions (default: True)
  • color: Whether to use ANSI colors
  • mix_stderr: Whether to mix stderr into stdout
Returns: A Click Result object with:
  • exit_code: The command’s exit code
  • output: The command’s output
  • exception: The exception raised (if any)
  • exc_info: Exception info tuple
If the obj argument is not given, passes an instance of flask.cli.ScriptInfo that knows how to load the Flask app being tested.

Version History

Added in version 1.0

EnvironBuilder

A helper for building WSGI environments with defaults from the Flask application.
from flask.testing import EnvironBuilder

builder = EnvironBuilder(
    app=app,
    path="/api/users",
    method="POST",
    json={"name": "John"}
)
env = builder.get_environ()

Constructor

def __init__(
    app: Flask,
    path: str = "/",
    base_url: str | None = None,
    subdomain: str | None = None,
    url_scheme: str | None = None,
    *args: Any,
    **kwargs: Any,
) -> None
app
Flask
required
The Flask application to configure the environment from.
path
str
default:"/"
URL path being requested.
base_url
str | None
default:"None"
Base URL where the app is being served, which path is relative to. If not given, built from PREFERRED_URL_SCHEME, subdomain, SERVER_NAME, and APPLICATION_ROOT.
subdomain
str | None
default:"None"
Subdomain name to append to SERVER_NAME. Cannot be used with base_url.
url_scheme
str | None
default:"None"
Scheme to use instead of PREFERRED_URL_SCHEME. Cannot be used with base_url.
args
Any
Additional positional arguments passed to werkzeug.test.EnvironBuilder.
kwargs
Any
Additional keyword arguments passed to werkzeug.test.EnvironBuilder:
  • method: HTTP method
  • data: Request body
  • query_string: URL query parameters
  • headers: Request headers
  • json: JSON data to serialize

Methods

json_dumps

def json_dumps(obj: Any, **kwargs: Any) -> str
Serialize obj to a JSON-formatted string using the app’s JSON configuration.
obj
Any
required
The object to serialize.
kwargs
Any
Additional arguments passed to the JSON encoder.
Returns: A JSON string.

Testing Examples

Basic Testing

import pytest
from myapp import create_app

@pytest.fixture
def app():
    app = create_app()
    app.config["TESTING"] = True
    return app

@pytest.fixture
def client(app):
    return app.test_client()

def test_index(client):
    response = client.get("/")
    assert response.status_code == 200
    assert b"Welcome" in response.data

def test_post_data(client):
    response = client.post("/login", data={
        "username": "test",
        "password": "secret"
    })
    assert response.status_code == 302

Testing JSON APIs

def test_json_api(client):
    # POST JSON
    response = client.post("/api/users", json={
        "name": "John",
        "email": "[email protected]"
    })
    assert response.status_code == 201
    data = response.get_json()
    assert data["name"] == "John"
    
    # GET JSON
    response = client.get("/api/users/1")
    assert response.status_code == 200
    assert response.content_type == "application/json"

Testing with Session

def test_session(client):
    # Set session data
    with client.session_transaction() as session:
        session["user_id"] = 1
    
    # Make request with session
    response = client.get("/profile")
    assert response.status_code == 200
    
    # Verify session data
    with client.session_transaction() as session:
        assert session["user_id"] == 1

Testing with Context

def test_request_context(client):
    with client:
        response = client.get("/")
        # Context is preserved
        assert request.path == "/"
        assert session.get("key") == "value"

Testing CLI Commands

import click

@app.cli.command()
def hello():
    """Say hello."""
    click.echo("Hello, World!")

def test_cli_command(app):
    runner = app.test_cli_runner()
    result = runner.invoke(args=["hello"])
    assert result.exit_code == 0
    assert "Hello, World!" in result.output

Testing File Uploads

import io

def test_upload(client):
    data = {
        "file": (io.BytesIO(b"file contents"), "test.txt")
    }
    response = client.post("/upload", 
                          data=data,
                          content_type="multipart/form-data")
    assert response.status_code == 200

Testing Redirects

def test_redirect(client):
    # Don't follow redirects
    response = client.get("/old-url")
    assert response.status_code == 302
    assert response.location == "/new-url"
    
    # Follow redirects
    response = client.get("/old-url", follow_redirects=True)
    assert response.status_code == 200
    assert response.request.path == "/new-url"

Custom Test Client

from flask.testing import FlaskClient

class CustomClient(FlaskClient):
    def __init__(self, *args, **kwargs):
        self._auth_token = kwargs.pop("auth_token", None)
        super().__init__(*args, **kwargs)
    
    def open(self, *args, **kwargs):
        if self._auth_token:
            headers = kwargs.setdefault("headers", {})
            headers["Authorization"] = f"Bearer {self._auth_token}"
        return super().open(*args, **kwargs)

app.test_client_class = CustomClient

def test_authenticated_request():
    client = app.test_client(auth_token="secret-token")
    response = client.get("/api/protected")
    assert response.status_code == 200

See Also

Build docs developers (and LLMs) love