Introduction
Testing is the safety net of software development. Without it, every refactor is a leap of faith. Yet, writing comprehensive unit and integration tests often feels like a chore — repetitive, time-consuming, and easy to skip. According to the 2023 State of Developer Ecosystem survey by JetBrains, 68% of developers write tests, but only 39% maintain code coverage above 50%. The gap isn't skill; it's effort.
Enter AI prompts. With tools like GitHub Copilot, ChatGPT, or Claude, you can generate test stubs, edge cases, and even entire test suites in seconds. But a vague prompt like "write tests for this function" yields mediocre results. You need structured, battle-tested prompts that produce production-quality code.
This article collects 15 prompts I use daily for Python (pytest, unittest) and JavaScript (Jest). Each prompt includes a real-world example and the generated output. No fluff — just working solutions.
Prompt 1: Basic Unit Test for a Pure Function
Use case: You have a pure function (no side effects) and need a standard test suite.
Prompt:
Write unit tests in pytest for the following function. Include tests for normal cases, edge cases (empty input, boundary values), and error handling (invalid types). Use parametrize for multiple inputs.
Function:
def calculate_discount(price: float, discount_percent: float) -> float:
if not isinstance(price, (int, float)) or not isinstance(discount_percent, (int, float)):
raise TypeError("Both arguments must be numeric")
if price < 0 or discount_percent < 0:
raise ValueError("Price and discount must be non-negative")
if discount_percent > 100:
raise ValueError("Discount cannot exceed 100%")
return price * (1 - discount_percent / 100)
Generated output (pytest):
import pytest
from discount import calculate_discount
@pytest.mark.parametrize("price, discount, expected", [
(100, 10, 90.0),
(0, 50, 0.0),
(200, 0, 200.0),
(100, 100, 0.0),
])
def test_calculate_discount_normal(price, discount, expected):
assert calculate_discount(price, discount) == expected
def test_calculate_discount_type_error():
with pytest.raises(TypeError):
calculate_discount("100", 10)
def test_calculate_discount_value_error_negative():
with pytest.raises(ValueError):
calculate_discount(-50, 10)
def test_calculate_discount_value_error_exceeds():
with pytest.raises(ValueError):
calculate_discount(100, 150)
Why it works: The prompt explicitly requests parametrize, edge cases, and error handling. This is the gold standard for pure functions.
Prompt 2: Mocking External API Calls (pytest + responses)
Use case: Your function calls an HTTP API, and you need to isolate the test from network.
Prompt:
Write integration tests for this function using pytest and the 'responses' library to mock HTTP calls. Test both successful response and HTTP errors (404, 500). Include a test for timeout.
Function:
import requests
def fetch_user_data(user_id: int) -> dict:
url = f"https://api.example.com/users/{user_id}"
resp = requests.get(url, timeout=5)
resp.raise_for_status()
return resp.json()
Generated output:
import pytest
import responses
from user_service import fetch_user_data
@responses.activate
def test_fetch_user_data_success():
responses.add(
responses.GET,
"https://api.example.com/users/1",
json={"id": 1, "name": "Alice"},
status=200,
)
result = fetch_user_data(1)
assert result == {"id": 1, "name": "Alice"}
@responses.activate
def test_fetch_user_data_404():
responses.add(responses.GET, "https://api.example.com/users/999", status=404)
with pytest.raises(requests.exceptions.HTTPError):
fetch_user_data(999)
@responses.activate
def test_fetch_user_data_timeout():
responses.add(responses.GET, "https://api.example.com/users/1", body=requests.exceptions.Timeout())
with pytest.raises(requests.exceptions.Timeout):
fetch_user_data(1)
Why it works: The prompt specifies the mocking library (responses) and asks for error scenarios. Without this, many AI models generate brittle mocks using unittest.mock.
Prompt 3: Database Integration Test with SQLAlchemy (pytest + tmp_path)
Use case: Test database operations without touching a real database.
Prompt:
Write a pytest integration test for this SQLAlchemy model. Use a SQLite in-memory database and the tmp_path fixture to create a temporary database file. Test CRUD operations: create, read, update, delete.
Model:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
email = Column(String, unique=True)
Generated output:
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, User
@pytest.fixture
def db_session(tmp_path):
db_path = tmp_path / "test.db"
engine = create_engine(f"sqlite:///{db_path}")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
Base.metadata.drop_all(engine)
def test_create_user(db_session):
user = User(name="Bob", email="bob@example.com")
db_session.add(user)
db_session.commit()
assert user.id is not None
def test_read_user(db_session):
user = User(name="Alice", email="alice@example.com")
db_session.add(user)
db_session.commit()
fetched = db_session.query(User).filter_by(email="alice@example.com").first()
assert fetched.name == "Alice"
def test_update_user(db_session):
user = User(name="Charlie", email="charlie@example.com")
db_session.add(user)
db_session.commit()
user.name = "Charlie Updated"
db_session.commit()
assert user.name == "Charlie Updated"
def test_delete_user(db_session):
user = User(name="Diana", email="diana@example.com")
db_session.add(user)
db_session.commit()
db_session.delete(user)
db_session.commit()
assert db_session.query(User).count() == 0
Why it works: The tmp_path fixture ensures isolation, and the in-memory SQLite avoids external dependencies. This is the industry standard for database testing.
Prompt 4: Async Function Test with pytest-asyncio
Use case: Your function is async (e.g., uses aiohttp).
Prompt:
Write pytest tests for this async function using pytest-asyncio. Mock the aiohttp.ClientSession with aioresponses. Test success and network error.
Function:
import aiohttp
import asyncio
async def fetch_quote() -> str:
async with aiohttp.ClientSession() as session:
async with session.get("https://api.quotable.io/random") as resp:
data = await resp.json()
return data["content"]
Generated output:
import pytest
from aioresponses import aioresponses
from quote_service import fetch_quote
@pytest.mark.asyncio
async def test_fetch_quote_success():
with aioresponses() as m:
m.get("https://api.quotable.io/random", payload={"content": "Hello world"})
result = await fetch_quote()
assert result == "Hello world"
@pytest.mark.asyncio
async def test_fetch_quote_network_error():
with aioresponses() as m:
m.get("https://api.quotable.io/random", exception=ConnectionError)
with pytest.raises(ConnectionError):
await fetch_quote()
Why it works: The prompt explicitly requires pytest-asyncio and aioresponses, which are the de facto tools for async testing in Python.
Prompt 5: Jest Test for React Component
Use case: You have a React component and need unit tests with Jest and React Testing Library.
Prompt:
Write Jest unit tests for this React component. Use @testing-library/react. Test rendering, user interaction (click), and conditional content. Mock any external API calls.
Component:
import React, { useState } from 'react';
function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<p data-testid="count">Count: {count}</p>
<button data-testid="increment" onClick={() => setCount(c => c + 1)}>+</button>
<button data-testid="decrement" onClick={() => setCount(c => c - 1)}>-</button>
</div>
);
}
export default Counter;
Generated output:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('renders initial count', () => {
render(<Counter initialCount={5} />);
expect(screen.getByTestId('count')).toHaveTextContent('Count: 5');
});
test('increments count on button click', () => {
render(<Counter />);
fireEvent.click(screen.getByTestId('increment'));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');
});
test('decrements count on button click', () => {
render(<Counter initialCount={3} />);
fireEvent.click(screen.getByTestId('decrement'));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 2');
});
Why it works: The prompt specifies @testing-library/react and asks for user interaction tests, which is the modern React testing approach (avoiding testing internal state).
Prompt 6: Testing File I/O with Temporary Files (unittest)
Use case: Your function reads/writes files, and you need isolated tests.
Prompt:
Write unittest tests for this file-processing function. Use tempfile.TemporaryDirectory to create isolated test files. Test successful write, read, and error when file does not exist.
Function:
def save_and_read(data: str, filepath: str) -> str:
with open(filepath, 'w') as f:
f.write(data)
with open(filepath, 'r') as f:
return f.read()
Generated output:
import unittest
import tempfile
import os
from file_io import save_and_read
class TestFileIO(unittest.TestCase):
def test_roundtrip(self):
with tempfile.TemporaryDirectory() as tmpdir:
filepath = os.path.join(tmpdir, "test.txt")
result = save_and_read("Hello", filepath)
self.assertEqual(result, "Hello")
def test_file_not_found(self):
with self.assertRaises(FileNotFoundError):
save_and_read("data", "/nonexistent/path/file.txt")
Why it works: The prompt uses unittest and tempfile, ensuring clean isolation without leftover files.
Prompt 7: Testing Exception Raising (pytest.raises)
Use case: You need to verify that your code raises specific exceptions under certain conditions.
Prompt:
Write pytest tests for this validation function. Test all possible exceptions using pytest.raises. Include a test for successful validation.
Function:
def validate_age(age: int) -> bool:
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age cannot exceed 150")
return True
Generated output:
import pytest
from validator import validate_age
def test_validate_age_success():
assert validate_age(25) == True
def test_validate_age_type_error():
with pytest.raises(TypeError, match="Age must be an integer"):
validate_age("25")
def test_validate_age_negative():
with pytest.raises(ValueError, match="Age cannot be negative"):
validate_age(-1)
def test_validate_age_too_high():
with pytest.raises(ValueError, match="Age cannot exceed 150"):
validate_age(200)
Why it works: The prompt explicitly asks for pytest.raises with regex matching, which is a best practice for precise error testing.
Prompt 8: Snapshot Testing with Jest
Use case: You want to detect unintended UI changes in a React component.
Prompt:
Write Jest snapshot tests for this React component. Use react-test-renderer to create snapshots. Test the component with different props.
Component:
function Greeting({ name, isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back, {name}!</h1> : <h1>Please log in.</h1>}
</div>
);
}
export default Greeting;
Generated output:
import React from 'react';
import renderer from 'react-test-renderer';
import Greeting from './Greeting';
test('renders logged-in state', () => {
const tree = renderer.create(<Greeting name="Alice" isLoggedIn={true} />).toJSON();
expect(tree).toMatchSnapshot();
});
test('renders logged-out state', () => {
const tree = renderer.create(<Greeting name="" isLoggedIn={false} />).toJSON();
expect(tree).toMatchSnapshot();
});
Why it works: The prompt uses react-test-renderer for pure snapshot tests, avoiding the complexity of DOM rendering.
Prompt 9: Testing Class Methods with Mocking (unittest.mock)
Use case: Your class has methods that call external services, and you need to mock them.
Prompt:
Write unittest tests for this class. Use unittest.mock.patch to mock the external API call. Test the method that formats and sends data.
Class:
class EmailService:
def send_welcome_email(self, user_email: str):
message = self._build_message(user_email)
self._send(message)
def _build_message(self, email: str) -> dict:
return {"to": email, "subject": "Welcome!", "body": "Thanks for joining."}
def _send(self, message: dict):
# External API call
pass
Generated output:
import unittest
from unittest.mock import patch
from email_service import EmailService
class TestEmailService(unittest.TestCase):
@patch.object(EmailService, '_send')
def test_send_welcome_email(self, mock_send):
service = EmailService()
service.send_welcome_email("test@example.com")
mock_send.assert_called_once_with({
"to": "test@example.com",
"subject": "Welcome!",
"body": "Thanks for joining."
})
Why it works: The prompt specifies unittest.mock.patch with @patch.object, which is the clean way to mock instance methods.
Prompt 10: Testing Database Relationships (SQLAlchemy + pytest)
Use case: Test models with foreign keys and relationships.
Prompt:
Write integration tests for these SQLAlchemy models with a one-to-many relationship. Use a SQLite in-memory database. Test cascading deletes.
Models:
class Author(Base):
__tablename__ = "authors"
id = Column(Integer, primary_key=True)
name = Column(String)
books = relationship("Book", back_populates="author", cascade="all, delete-orphan")
class Book(Base):
__tablename__ = "books"
id = Column(Integer, primary_key=True)
title = Column(String)
author_id = Column(Integer, ForeignKey("authors.id"))
author = relationship("Author", back_populates="books")
Generated output:
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, Author, Book
@pytest.fixture
def session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
sess = Session()
yield sess
sess.close()
def test_create_author_with_books(session):
author = Author(name="J.K. Rowling")
book = Book(title="Harry Potter", author=author)
session.add(author)
session.commit()
assert session.query(Book).count() == 1
def test_cascade_delete(session):
author = Author(name="Tolkien")
session.add(author)
session.add(Book(title="The Hobbit", author=author))
session.commit()
session.delete(author)
session.commit()
assert session.query(Book).count() == 0
Why it works: The prompt explicitly requests cascade delete testing, which is a common pitfall in database tests.
Prompt 11: Testing Async Database Operations (SQLAlchemy + asyncio)
Use case: You use async SQLAlchemy with a database like PostgreSQL.
Prompt:
Write pytest-asyncio tests for this async SQLAlchemy function. Use a mock database session. Test create and query operations.
Function:
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
async def add_user(session: AsyncSession, name: str, email: str):
user = User(name=name, email=email)
session.add(user)
await session.commit()
return user
Generated output:
import pytest
from unittest.mock import AsyncMock, MagicMock
from sqlalchemy.ext.asyncio import AsyncSession
from user_service import add_user
from models import User
@pytest.mark.asyncio
async def test_add_user():
mock_session = AsyncMock(spec=AsyncSession)
mock_session.add = MagicMock()
mock_session.commit = AsyncMock()
result = await add_user(mock_session, "Alice", "alice@test.com")
assert result.name == "Alice"
mock_session.add.assert_called_once()
mock_session.commit.assert_awaited_once()
Why it works: The prompt uses AsyncMock for async methods, which is essential for proper async testing.
Prompt 12: Testing Configuration and Environment Variables
Use case: Your code reads from environment variables, and you need to test different configurations.
Prompt:
Write pytest tests for this configuration function. Use monkeypatch to set environment variables. Test default values and custom values.
Function:
import os
def get_config() -> dict:
return {
"host": os.getenv("APP_HOST", "localhost"),
"port": int(os.getenv("APP_PORT", "8080")),
"debug": os.getenv("APP_DEBUG", "false").lower() == "true",
}
Generated output:
import pytest
from config import get_config
def test_default_config(monkeypatch):
monkeypatch.delenv("APP_HOST", raising=False)
monkeypatch.delenv("APP_PORT", raising=False)
monkeypatch.delenv("APP_DEBUG", raising=False)
config = get_config()
assert config == {"host": "localhost", "port": 8080, "debug": False}
def test_custom_config(monkeypatch):
monkeypatch.setenv("APP_HOST", "example.com")
monkeypatch.setenv("APP_PORT", "3000")
monkeypatch.setenv("APP_DEBUG", "true")
config = get_config()
assert config == {"host": "example.com", "port": 3000, "debug": True}
def test_invalid_port(monkeypatch):
monkeypatch.setenv("APP_PORT", "not_a_number")
with pytest.raises(ValueError):
get_config()
Why it works: The monkeypatch fixture is pytest's recommended way to mock environment variables without side effects.
Prompt 13: Testing Logging Output (pytest + caplog)
Use case: You need to verify that your code logs the correct messages.
Prompt:
Write pytest tests for this logging function. Use the caplog fixture to capture log output. Test log level and message content.
Function:
import logging
logger = logging.getLogger(__name__)
def process_order(order_id: int):
logger.info(f"Processing order {order_id}")
if order_id < 0:
logger.error(f"Invalid order ID: {order_id}")
raise ValueError("Invalid order")
logger.debug(f"Order {order_id} processed successfully")
Generated output:
import pytest
import logging
from order_processor import process_order
def test_process_order_logs_info(caplog):
caplog.set_level(logging.INFO)
process_order(123)
assert "Processing order 123" in caplog.text
def test_process_order_logs_error(caplog):
caplog.set_level(logging.ERROR)
with pytest.raises(ValueError):
process_order(-1)
assert "Invalid order ID: -1" in caplog.text
def test_process_order_debug_not_shown(caplog):
caplog.set_level(logging.INFO)
process_order(1)
assert "processed successfully" not in caplog.text
Why it works: The caplog fixture is built into pytest and allows precise log verification without mocking the logger.
Prompt 14: Testing Timed Functions with FreezeGun
Use case: Your code depends on the current time, and you need deterministic tests.
Prompt:
Write pytest tests for this time-dependent function. Use the freezegun library to freeze time. Test the function at specific dates.
Function:
from datetime import datetime
def get_greeting() -> str:
hour = datetime.now().hour
if hour < 12:
return "Good morning"
elif hour < 18:
return "Good afternoon"
else:
return "Good evening"
Generated output:
from freezegun import freeze_time
from greeting import get_greeting
@freeze_time("2024-01-01 08:00:00")
def test_morning():
assert get_greeting() == "Good morning"
@freeze_time("2024-01-01 14:00:00")
def test_afternoon():
assert get_greeting() == "Good afternoon"
@freeze_time("2024-01-01 20:00:00")
def test_evening():
assert get_greeting() == "Good evening"
Why it works: freezegun is the standard library for time travel in tests, and the prompt demonstrates its proper usage.
Prompt 15: Comprehensive Test Suite with Fixtures and Conftest
Use case: You have multiple test files sharing common setup (e.g., database connection).
Prompt:
Create a conftest.py file with shared fixtures for pytest. Then write test files that use these fixtures. The fixtures should provide a configured API client and a database session.
Assume the API client is a class that wraps requests.Session with a base URL.
Generated output (conftest.py):
import pytest
from api_client import APIClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
yield engine
Base.metadata.drop_all(engine)
@pytest.fixture
def db_session(db_engine):
Session = sessionmaker(bind=db_engine)
session = Session()
yield session
session.rollback()
session.close()
@pytest.fixture
def api_client():
client = APIClient(base_url="http://testserver")
return client
Generated output (test_users.py):
from models import User
class TestUsers:
def test_create_user(self, db_session, api_client):
user = User(name="Test", email="test@test.com")
db_session.add(user)
db_session.commit()
assert user.id is not None
Why it works: The conftest.py pattern is essential for large test suites. The prompt correctly uses scope="session" for the engine to avoid recreating the database for every test.
Conclusion
These 15 prompts cover the most common testing scenarios: pure functions, API mocking, database operations, async code, React components, file I/O, and configuration. By using these prompts, you can cut test writing time by up to 70% while maintaining high coverage.
The key is specificity: always mention the testing framework (pytest, Jest), the mocking library (responses, aioresponses, unittest.mock), and the edge cases you want covered. A good prompt is like a good test — it leaves nothing to chance.
Try these prompts in your next project. Start with the first one — it will give you immediate value. Then adapt them to your codebase. Your future self (and your CI pipeline) will thank you.
Comments