Writing Tests and Documentation with Copilot
Two tasks that developers often defer - writing tests and documentation - become much faster with Copilot. This lesson shows you practical techniques for generating tests and docs efficiently.
Generating Unit Tests
The Basic Approach
Open your implementation file alongside a test file. Copilot uses context from both:
// In mathUtils.js
export function calculateDiscount(price, discountPercent) {
if (discountPercent < 0 || discountPercent > 100) {
throw new Error('Discount must be between 0 and 100');
}
return price * (1 - discountPercent / 100);
}
// In mathUtils.test.js - write a describe block and let Copilot fill in tests
describe('calculateDiscount', () => {
// Copilot suggests:
it('should calculate discount correctly', () => {
expect(calculateDiscount(100, 20)).toBe(80);
});
it('should return original price for 0% discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});
it('should throw error for negative discount', () => {
expect(() => calculateDiscount(100, -10)).toThrow();
});
it('should throw error for discount over 100', () => {
expect(() => calculateDiscount(100, 150)).toThrow();
});
});
Using the /tests Command
Select a function and use Copilot Chat:
# Select this function
def validate_email(email):
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
# Type: /tests
Copilot generates comprehensive tests:
import pytest
from your_module import validate_email
class TestValidateEmail:
def test_valid_simple_email(self):
assert validate_email('user@example.com') == True
def test_valid_email_with_subdomain(self):
assert validate_email('user@mail.example.com') == True
def test_valid_email_with_plus(self):
assert validate_email('user+tag@example.com') == True
def test_invalid_email_no_at(self):
assert validate_email('userexample.com') == False
def test_invalid_email_no_domain(self):
assert validate_email('user@') == False
def test_invalid_email_empty_string(self):
assert validate_email('') == False
def test_invalid_email_spaces(self):
assert validate_email('user @example.com') == False
Test Patterns Copilot Handles Well
Testing async functions:
// Implementation
async function fetchUser(id: string): Promise<User | null> {
const response = await api.get(`/users/${id}`);
return response.data;
}
// Tests Copilot generates:
describe('fetchUser', () => {
it('should fetch user successfully', async () => {
const mockUser = { id: '1', name: 'John' };
api.get.mockResolvedValue({ data: mockUser });
const result = await fetchUser('1');
expect(result).toEqual(mockUser);
expect(api.get).toHaveBeenCalledWith('/users/1');
});
it('should return null for non-existent user', async () => {
api.get.mockResolvedValue({ data: null });
const result = await fetchUser('999');
expect(result).toBeNull();
});
});
Testing with mocks:
// Tell Copilot what to mock in a comment:
// Test fetchUserData, mocking the database.query function
describe('fetchUserData', () => {
it('should return user data from database', async () => {
const mockQuery = jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]);
const db = { query: mockQuery };
const result = await fetchUserData(db, 1);
expect(result.name).toBe('Alice');
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM users WHERE id = ?', [1]);
});
});
Edge case testing:
# Comment: Generate tests including edge cases for the divide function
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Copilot generates:
class TestDivide:
def test_positive_numbers(self):
assert divide(10, 2) == 5
def test_negative_numbers(self):
assert divide(-10, 2) == -5
def test_divide_by_zero_raises_error(self):
with pytest.raises(ValueError):
divide(10, 0)
def test_float_division(self):
assert divide(7, 2) == 3.5
def test_zero_numerator(self):
assert divide(0, 5) == 0
Generating Documentation
Function Documentation
Position your cursor before a function and trigger Copilot:
# Type: """ and press Enter for docstring suggestions
def calculate_compound_interest(principal, rate, years, compounds_per_year=12):
"""
Calculate compound interest for an investment.
Args:
principal (float): The initial investment amount
rate (float): Annual interest rate as a decimal (e.g., 0.05 for 5%)
years (int): Number of years to compound
compounds_per_year (int): Number of times interest compounds per year
(default: 12 for monthly compounding)
Returns:
float: The final amount after compound interest
Example:
>>> calculate_compound_interest(1000, 0.05, 10)
1647.01
"""
return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * years)
JSDoc Comments
// Type: /** and press Enter
/**
* Validates and processes a user registration form
* @param {Object} formData - The form submission data
* @param {string} formData.email - User's email address
* @param {string} formData.password - User's chosen password
* @param {string} formData.name - User's display name
* @returns {Promise<{success: boolean, userId?: string, errors?: string[]}>}
* @throws {Error} If database connection fails
* @example
* const result = await processRegistration({
* email: 'user@example.com',
* password: 'securePass123',
* name: 'John Doe'
* });
*/
async function processRegistration(formData) {
// implementation
}
TypeScript/TSDoc
/**
* Represents a paginated API response
* @template T - The type of items in the data array
*/
interface PaginatedResponse<T> {
/** Array of items for the current page */
data: T[];
/** Current page number (1-indexed) */
page: number;
/** Total number of pages available */
totalPages: number;
/** Total count of all items */
totalCount: number;
/** Whether there are more pages after this one */
hasNextPage: boolean;
}
README Generation
Ask Copilot Chat to generate README sections:
"Generate a README.md for my project with:
- A brief description (it's a Node.js CLI tool for converting images)
- Installation instructions
- Usage examples
- Configuration options
- Contributing guidelines"
API Documentation
// Use comments to guide Copilot for API docs:
/**
* @api {post} /users Create new user
* @apiName CreateUser
* @apiGroup Users
* @apiVersion 1.0.0
*
* @apiBody {String} email User's email address
* @apiBody {String} password User's password (min 8 chars)
* @apiBody {String} [name] User's display name
*
* @apiSuccess {String} id Unique user ID
* @apiSuccess {String} email User's email
* @apiSuccess {Date} createdAt Account creation timestamp
*
* @apiError (400) ValidationError Invalid input data
* @apiError (409) DuplicateEmail Email already registered
*/
app.post('/users', async (req, res) => {
// implementation
});
Inline Comments
Copilot can add explanatory comments to complex code:
# Ask Copilot: "Add inline comments explaining each step"
def quicksort(arr):
# Base case: arrays with 0 or 1 element are already sorted
if len(arr) <= 1:
return arr
# Choose the middle element as pivot to avoid worst-case on sorted arrays
pivot = arr[len(arr) // 2]
# Partition into three groups: less than, equal to, greater than pivot
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
# Recursively sort left and right, then combine
return quicksort(left) + middle + quicksort(right)
Test-Documentation Workflow
A productive workflow combines both:
- Write the function with Copilot's help
- Generate tests using /tests or inline suggestions
- Run tests to verify the implementation
- Add documentation with Copilot
- Review everything for accuracy
// Step 1: Write function with Copilot
// Function to format a phone number: (xxx) xxx-xxxx
function formatPhoneNumber(phone) {
const cleaned = phone.replace(/\D/g, '');
if (cleaned.length !== 10) return null;
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
}
// Step 2: Generate tests
describe('formatPhoneNumber', () => {
it('formats valid 10-digit number', () => {
expect(formatPhoneNumber('1234567890')).toBe('(123) 456-7890');
});
it('handles numbers with existing formatting', () => {
expect(formatPhoneNumber('123-456-7890')).toBe('(123) 456-7890');
});
it('returns null for invalid length', () => {
expect(formatPhoneNumber('123456')).toBeNull();
});
});
// Step 3: Add documentation
/**
* Formats a phone number string to (xxx) xxx-xxxx format
* @param {string} phone - Raw phone number (digits, dashes, spaces allowed)
* @returns {string|null} Formatted phone number or null if invalid
*/
Tips for Better Test Generation
Provide Test Examples First
// Show Copilot the pattern you want:
describe('validatePassword', () => {
it('should return true for valid password with 8+ chars, number, and special char', () => {
expect(validatePassword('MyPass1!')).toBe(true);
});
// Now Copilot continues with similar test patterns...
});
Name Test Files Clearly
userService.test.ts helps Copilot understand it should generate tests for userService.ts.
Keep Implementation Visible
Open the implementation file in a split view - Copilot references it for context.
Tips for Better Documentation
Start with the Docstring Syntax
Type """ (Python), /** (JS/TS), or /// (C#) to trigger documentation suggestions.
Include an Example
When you type Example: or @example, Copilot generates usage examples:
def merge_dicts(dict1, dict2):
"""
Merge two dictionaries, with dict2 values taking precedence.
Example:
>>> merge_dicts({'a': 1, 'b': 2}, {'b': 3, 'c': 4})
{'a': 1, 'b': 3, 'c': 4}
"""
Summary
Copilot accelerates test and documentation writing:
For Tests:
- Use
/testscommand in Chat for comprehensive test generation - Open implementation files alongside test files for context
- Guide Copilot with example tests showing your preferred style
- Review generated tests for edge cases you might have missed
For Documentation:
- Start with your language's doc comment syntax (
/**,""", etc.) - Include
@param,@returns, and@examplefor complete docs - Use Chat to generate README sections and API documentation
- Add inline comments to complex code
These are areas where AI assistance provides enormous time savings while ensuring you don't skip these important but often-neglected tasks.
Next, you'll learn best practices for reviewing AI-generated code to ensure quality and security.

