Stateful graphs can keep routing and checkpoint bugs hidden until a particular input follows the affected path. Automated tests make node transformations and the compiled graph result fail loudly before the behavior reaches an agent workflow.
A function-scoped pytest fixture builds a fresh graph and InMemorySaver for every test, preventing one test's thread state from affecting another. Invoking a compiled node through graph.nodes isolates its transformation and bypasses the graph checkpointer.
The sample uses deterministic string transforms rather than a model call, so it needs no credentials and returns the same assertions on every run. The final test invokes the compiled path with a thread ID and checks the state returned after both nodes.
from typing_extensions import TypedDict from langgraph.graph import END, START, StateGraph class GraphState(TypedDict): text: str def normalize_text(state: GraphState) -> GraphState: return {"text": state["text"].strip().lower()} def label_text(state: GraphState) -> GraphState: return {"text": f"processed:{state['text']}"}
def build_graph() -> StateGraph: builder = StateGraph(GraphState) builder.add_node("normalize", normalize_text) builder.add_node("label", label_text) builder.add_edge(START, "normalize") builder.add_edge("normalize", "label") builder.add_edge("label", END) return builder
import pytest from langgraph.checkpoint.memory import InMemorySaver from graph_app import build_graph @pytest.fixture def compiled_graph(): return build_graph().compile(checkpointer=InMemorySaver())
A new InMemorySaver keeps checkpoint state isolated for each test that requests the fixture.
def test_normalize_node(compiled_graph): result = compiled_graph.nodes["normalize"].invoke({"text": " READY "}) assert result == {"text": "ready"}
Calling a node through compiled_graph.nodes tests that node directly without running connected edges or the checkpointer.
def test_compiled_graph(compiled_graph): result = compiled_graph.invoke( {"text": " READY "}, config={"configurable": {"thread_id": "graph-test"}}, ) assert result == {"text": "processed:ready"}
$ pytest -q .. [100%] 2 passed in 0.34s
Both dots must reach 100%, and the summary must report two passed tests. Any failed assertion identifies either the isolated node behavior or the compiled graph path that changed.