AGENTS.md@tests · git:20260518.8456ac4 · 2026-05-18 · sha256 e8156187c31813f2
AGENTS.md@tests git:20260518.8456ac4A
Immutable. This exact content is served forever at /api/v1/blob/e8156187c31813f2.
# Skills Testing Guide
> **For AI Agents**: This document provides patterns and conventions for creating and maintaining tests for Azure Copilot skills. When asked to "scaffold tests" for a skill, follow the instructions below.
## Scaffolding Tests for a Skill
When a user asks to scaffold, create, or add tests for a skill, follow these steps:
### Step 1: Copy the template
```bash
cp -r tests/_template tests/{skill-name}
```
> **For AI Agents**: Do not add or change files outside of the new test directory.
### Step 2: Read the skill's SKILL.md
Load the file at `plugin/skills/{skill-name}/SKILL.md` to understand:
- The skill's name and description (from frontmatter)
- What the skill does (from content)
- What Azure services/tools it references
**Also check for references:** If the skill has a `references/` folder, note the structure:
- `references/recipes/` - Multiple implementation approaches (azd, bicep, terraform)
- `references/services/` - Multiple Azure services the skill supports
- References load only when explicitly linked, so understand what paths SKILL.md links to
> **Frontmatter Format Rule:** Descriptions over 200 characters MUST use folded YAML format (`>-`) for maintainability. The `>-` format keeps descriptions readable in source while parsing to a flat string compatible with skills.sh and other registries. Do NOT use `|` (literal block) as it preserves newlines.
### Step 3: Update test files
In each test file `triggers.test.ts`, change:
```typescript
const SKILL_NAME = '{skill-name}'; // Must match the folder name exactly
```
### Step 4: Generate trigger prompts
Based on the skill's description and content, add to `triggers.test.ts`:
**shouldTriggerPrompts** (at least 5) - prompts that mention:
- The skill's primary Azure service (e.g., "Redis", "Cosmos DB", "Key Vault")
- Common tasks the skill helps with
- Keywords from the skill's description
**shouldNotTriggerPrompts** (at least 5) - prompts about:
- Unrelated topics ("weather", "poetry")
- Different cloud providers ("AWS", "GCP")
- Different Azure services not covered by this skill
### Step 5: Configure integration tests (optional)
In `integration.test.ts`, customize the prompts to test real agent behavior.
Follow existing `integration.test.ts` files for how to implement such tests.
### Step 6: Run and verify
```bash
cd tests
# Run all tests (integration runs if SDK available)
npm test -- --testPathPatterns={skill-name}
# Skip integration tests explicitly
SKIP_INTEGRATION_TESTS=true npm test -- --testPathPatterns={skill-name}
```
### Step 8: Update coverage grid
```bash
npm run coverage:grid
```
---
## Overview
This testing framework uses **Jest** to validate skill behavior across these test categories:
- **Trigger Tests** - Skill activation validation
- **Integration Tests** - MCP tool interaction testing
## Quick Reference: Test File Conventions
---
## Test File Conventions
### File Naming
| File | Purpose |
|------|---------|
| `triggers.test.ts` | Tests skill activation on prompts |
| `integration.test.ts` | Tests real Copilot agent behavior (optional) |
| `fixtures/*.json` | Test data and mock responses |
### Directory Structure
```
tests/{skill-name}/
├── unit.test.js
├── triggers.test.js
├── integration.test.js # Optional - requires Copilot CLI auth
├── __snapshots__/ # Auto-generated by Jest
│ └── triggers.test.js.snap
└── fixtures/
└── prompts.json # Trigger test prompts
```
---
## Writing Trigger Tests
Trigger tests verify that prompts correctly activate (or don't activate) your skill.
### Parameterized Tests
Use `test.each` for testing multiple prompts:
```typescript
const shouldTriggerPrompts = [
'How do I deploy to Azure App Service?',
'Configure my Azure storage account',
'Help with Azure CLI commands',
];
test.each(shouldTriggerPrompts)(
'triggers on: "%s"',
(prompt) => {
const result = triggerMatcher.shouldTrigger(prompt);
expect(result.triggered).toBe(true);
}
);
```
### Snapshot Tests
Snapshots catch unintended changes to trigger behavior:
```typescript
test('skill keywords match snapshot', () => {
expect(triggerMatcher.getKeywords()).toMatchSnapshot();
});
```
### Updating Snapshots
When trigger behavior intentionally changes:
```bash
npm run update:snapshots -- --testPathPatterns=your-skill-name
```
**Always review snapshot changes before committing!**
---
## Using Fixtures
### Loading Fixtures
```typescript
import { loadFixtures, loadFixture } from '../utils/fixtures';
// Load all fixtures for a skill
const fixtures = loadFixtures('azure-validation');
// Load a specific fixture
const prompts = loadFixture('azure-validation', 'prompts');
```
### Fixture File Format
`fixtures/prompts.json`:
```json
{
"shouldTrigger": [
"Deploy to Azure",
"Configure storage account"
],
"shouldNotTrigger": [
"Help with AWS",
"Write a poem"
]
}
```
---
## Writing Integration Tests
Integration tests run a real Copilot agent session to verify skill behavior.
### Prerequisites
1. Install Copilot CLI: `npm install -g @github/copilot-cli`
2. Authenticate: Run `copilot` and follow prompts
### Basic Integration Test
```typescript
import {
run,
isSkillInvoked,
doesAssistantMessageIncludeKeyword,
shouldSkipIntegrationTests,
getIntegrationSkipReason
} from '../utils/agent-runner';
const SKILL_NAME = 'azure-rbac';
// Integration tests auto-skip if SDK unavailable or in CI
const skipTests = shouldSkipIntegrationTests();
const skipReason = getIntegrationSkipReason();
if (skipTests && skipReason) {
console.log(`⏭️ Skipping integration tests: ${skipReason}`);
}
const describeIntegration = skipTests ? describe.skip : describe;
describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => {
test('invokes skill for relevant prompt', async () => {
const agentMetadata = await run({
prompt: 'What role should I assign for blob storage access?'
});
expect(isSkillInvoked(agentMetadata, SKILL_NAME)).toBe(true);
expect(doesAssistantMessageIncludeKeyword(agentMetadata, 'Storage Blob')).toBe(true);
});
});
```
### Agent Runner Helpers
| Helper | Purpose |
|--------|---------|
| `agentRunner.run(config)` | Execute agent session with prompt |
| `agentRunner.isSkillInvoked(metadata, skillName)` | Check if skill was invoked |
| `agentRunner.areToolCallsSuccess(metadata, toolName)` | Check if tool calls succeeded |
| `agentRunner.doesAssistantMessageIncludeKeyword(metadata, keyword)` | Search response for keyword |
### Test with Workspace Setup
```javascript
test('works with project files', async () => {
if (shouldSkip()) return;
const agentMetadata = await agentRunner.run({
setup: async (workspace) => {
const fs = require('fs');
const path = require('path');
fs.writeFileSync(path.join(workspace, 'main.bicep'), 'resource ...');
},
prompt: 'Validate my Bicep file'
});
expect(agentRunner.isSkillInvoked(agentMetadata, 'azure-validation')).toBe(true);
});
```
---
## Running Tests
### Local Development
```bash
# Run all tests (integration runs if SDK available, skips if not)
npm test
# Unit and trigger tests only (always skips integration)
npm run test:unit
# Skip integration tests explicitly
SKIP_INTEGRATION_TESTS=true npm test
# Specific skill
npm test -- --testPathPatterns=azure-validation
# Watch mode
npm run test:watch -- --testPathPatterns=azure-validation
# With coverage
npm run test:coverage -- --testPathPatterns=azure-validation
# Verbose output
npm run test:verbose
```
### CI Environment
Tests automatically run on:
- Push to `main` affecting skill or test files
- Pull requests affecting skill or test files
- Manual workflow dispatch
Output formats:
- **Console**: Human-readable Jest output
- **CI**: JUnit XML in `tests/reports/junit.xml`
- **PR**: Annotations via GitHub Actions
---
## Coverage Requirements
### Minimum Thresholds
| Metric | Target |
|--------|--------|
| Statements | 60% |
| Branches | 50% |
| Functions | 60% |
| Lines | 60% |
### Checking Coverage
```bash
npm run test:coverage
```
Coverage reports are generated in `tests/coverage/`.
---
## GitHub Actions Integration
### Running Tests in CI
Use the **workflow_dispatch** trigger on `test-all-skills.yml`:
1. Go to **Actions** → **Test All Skills**
2. Click **Run workflow**
3. Enter a skill name (e.g., `azure-validation`) or leave empty for all skills
### Full Test Suite
`.github/workflows/test-all-skills.yml` runs all skill tests and updates the README coverage grid.
---
## Troubleshooting
### Test Not Running
1. Check skill name matches folder name exactly
2. Verify test file ends with `.test.js`
3. Check `testPathIgnorePatterns` in `jest.config.js`
### Snapshot Mismatch
1. Review the diff carefully
2. If change is intentional: `npm run update:snapshots`
3. If change is unintentional: investigate and fix
---
## Best Practices
1. **One assertion per test** when possible for clear failure messages
2. **Descriptive test names**: `test('rejects storage names over 24 characters')`
3. **Test edge cases**: Empty input, very long input, special characters
4. **Keep fixtures small**: Only include data needed for the test
5. **Review snapshots**: Don't blindly update—understand the change
6. **Clean up mocks**: Reset between tests to prevent interference
---
## Adding Tests Checklist
When adding tests for a new skill:
- [ ] Copy `_template/` to `tests/{skill-name}/`
- [ ] Update `SKILL_NAME` in all test files
- [ ] Add 5+ prompts that should trigger
- [ ] Add 5+ prompts that should NOT trigger
- [ ] Run tests locally and verify passing
- [ ] Update coverage grid if significant changes
---
*Last updated: Auto-generated by skill testing framework*