AI-Assisted Testing & Tools
Testing AI Features: LLM Outputs and Prompts
Test non-deterministic AI features using mocking, contract testing, behavioral assertions, and evaluation strategies.
The AI Testing Challenge
AI features present testing challenges that don't exist for deterministic code:
- LLM outputs are non-deterministic
- Quality is subjective
- Calling real AI APIs in tests is slow and expensive
- Streaming responses require special handling
Four strategies address these challenges: mock, contract, behavioral, and evaluation testing.
Strategy 1: Mock the AI
For unit and integration tests, replace the LLM call with a deterministic mock. This tests your application code without calling the real AI API — fast, free, and deterministic.
Strategy 2: Contract Testing
Verify your code correctly handles the expected response format, regardless of content. Mock the response with a known structure and assert your parsing code extracts the right fields.
Strategy 3: Behavioral Testing
Test that your system behaves correctly given any plausible AI response:
- Handle very long responses without breaking layout
- Show error message when AI API fails
- Handle empty responses gracefully
- Handle malformed JSON responses
Strategy 4: Evaluation Testing (Prompt Regression)
For testing actual AI quality, maintain a test set of prompts with expected output characteristics. Mark these as skip by default and run them separately since they call real APIs.
Testing Streaming Responses
Create a ReadableStream with test chunks encoding SSE format, mock fetch to return it, call the streaming function, and assert the assembled result matches expected output.
Key Takeaways
- Mock the AI for unit and integration tests — fast, free, deterministic
- Contract tests verify your code handles the response format correctly regardless of content
- Behavioral tests verify your application handles any plausible AI response gracefully
- Evaluation tests check actual AI quality — run these separately from your fast test suite
- Test your guardrails explicitly: verify harmful content is blocked, PII is not exposed
Example
// Testing AI guardrails
describe('ContentModerationGuardrail', () => {
it('blocks responses containing harmful content patterns', async () => {
vi.mocked(ai.generateResponse).mockResolvedValue({
content: 'Here are instructions for making explosives: ...',
});
const response = await getChatResponse('how to make something');
expect(response.blocked).toBe(true);
expect(response.content).toBe('I cannot help with that request.');
});
it('redacts PII from AI responses', async () => {
vi.mocked(ai.generateResponse).mockResolvedValue({
content: 'The user John Smith (SSN: 123-45-6789) has...',
});
const response = await getChatResponse('tell me about the user');
expect(response.content).not.toContain('123-45-6789');
expect(response.content).toContain('[REDACTED]');
});
});// Testing AI guardrails
describe('ContentModerationGuardrail', () => {
it('blocks responses containing harmful content patterns', async () => {
vi.mocked(ai.generateResponse).mockResolvedValue({
content: 'Here are instructions for making explosives: ...',
});
const response = await getChatResponse('how to make something');
expect(response.blocked).toBe(true);
expect(response.content).toBe('I cannot help with that request.');
});
it('redacts PII from AI responses', async () => {
vi.mocked(ai.generateResponse).mockResolvedValue({
content: 'The user John Smith (SSN: 123-45-6789) has...',
});
const response = await getChatResponse('tell me about the user');
expect(response.content).not.toContain('123-45-6789');
expect(response.content).toContain('[REDACTED]');
});
});