Testing checks whether an AI web application behaves as intended, from a chat component’s input handling to the flow between an API route and a model. Use unit tests to isolate individual functions and components; use integration tests to check how components, routes, and external services work together. A passing unit test does not establish that the whole application works. section 8.4.1 section 2.5.6
Choose checks around the behavior users and application code depend on:
- For React components, check rendering under different conditions, state updates, and responses to events such as typing or submitting a form. section 8.4.1 section 2.5.6
- For integrated flows, check that an API route sends a request and handles the response; include errors, edge cases, asynchronous behavior, and streaming where relevant. section 8.4.1 section 8.5
- For AI-dependent code, replace external responses with controlled values. Test application behavior against those values rather than depending on a live model for every run. section 5.1.3 section 8.4.4
Run a chat component’s unit tests
From the repository root, run `npm run test:unit -w ch02/chat-client`. The example suite checks an initial message, input changes while typing, and the loading state while a response is fetched. Treat these as a starting set of use cases, not exhaustive coverage. section 2.5.6
Generated text needs more flexible checks than a single exact expected string. Model outputs can vary, and repeated inference can be slow or costly. With a mocked response, compare its semantic similarity to an expected answer or validate explicit constraints such as word count and required terms. Keep prompt changes testable so an edit does not unexpectedly change system behavior. section 5.1.3
Listing 5.4 Testing constraints
function validateConstraints(text, minWords, maxWords, requiredWords) { #1
const words = text.split(/\s+/);
return (
words.length >= minWords &&
words.length <= maxWords &&
requiredWords.every((word) =>
text.toLowerCase().includes(word.toLowerCase())
)
);
}
test("generated text meets constraints", async () => {
generateText.mockResolvedValue({
text: "Regular exercise significantly improves
health and fitness by boosting energy, enhancing mood,
and reducing the risk of many chronic diseases."
});
const { text } = await generateText({
model: "fakeModel",
prompt:
"Summarize the benefits of exercise in 20-30 words.
Include 'health' and 'fitness'.",#2
});
console.log("Word count:", text.split(/\s+/).length);
expect(validateConstraints(text, 20, 30, ["health", "fitness"])).toBe(true);#3
});
The constraint test supplies a controlled generated response, then checks its word-count range and required words. It makes the acceptance rule visible without requiring an exact prose match. section 5.1.3
For LangChain.js workflows, mock dependencies at the application boundary. `FakeRetriever` can return fixed documents and `FakeLLM` can return a fixed answer, allowing a retrieval-augmented generation method to be checked for both its answer path and an uninitialized-retriever error. Test document-processing functions separately, then use integration tests for chains that combine them. When a test fails, also inspect mock formats, asynchronous handling, streaming, and emitted events. section 8.4.4 section 8.5




















































