Testing Terminology
A comprehensive glossary of testing terms and concepts every developer should know.
Core Testing Concepts
Assertion
A statement that verifies expected behavior in a test. If the assertion fails, the test fails.
// Examples of assertions
expect(result).toBe(5)
expect(user.name).toEqual('John Doe')
expect(array).toHaveLength(3)
expect(element).toBeInTheDocument()Different testing frameworks provide different assertion APIs, but the concept remains the same: verify that actual values match expected values.
Mock
A simulated object that mimics the behavior of real objects in controlled ways. Mocks record how they are called and can verify interactions.
import { vi } from 'vitest'
// Create a mock function
const mockFn = vi.fn()
// Use the mock
mockFn('hello', 123)
// Verify it was called correctly
expect(mockFn).toHaveBeenCalledWith('hello', 123)
expect(mockFn).toHaveBeenCalledTimes(1)Mocking modules:
// Mock an entire module
vi.mock('./api', () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John' })),
}))Stub
A test double that provides predefined responses to calls, without recording any information about how it was called.
// Stub example
const userServiceStub = {
getUser: () => ({ id: 1, name: 'Test User' }),
saveUser: () => true,
}
// Use in test
const user = userServiceStub.getUser()
expect(user.name).toBe('Test User')Difference between Mock and Stub:
- Mock: Verifies how it was called (behavior verification)
- Stub: Just returns values (state verification)
Spy
A function that records information about calls made to it while still calling the original implementation.
import { vi } from 'vitest'
const calculator = {
add: (a, b) => a + b,
}
// Create a spy
const addSpy = vi.spyOn(calculator, 'add')
// Use it
const result = calculator.add(2, 3)
// Verify both the result AND the call
expect(result).toBe(5) // Original function worked
expect(addSpy).toHaveBeenCalledWith(2, 3) // Call was recordedFixture
A fixed state of data or objects used to ensure tests are reproducible and run in a known environment.
// Test fixture
const userFixture = {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'admin',
}
// Use in multiple tests
describe('User tests', () => {
it('should format user name', () => {
expect(formatName(userFixture)).toBe('Test User')
})
it('should check admin role', () => {
expect(isAdmin(userFixture)).toBe(true)
})
})Test Suite
A collection of related test cases grouped together.
// Test suite using describe
describe('Calculator', () => {
// Individual test cases
it('should add numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('should subtract numbers', () => {
expect(subtract(5, 3)).toBe(2)
})
// Nested suite
describe('division', () => {
it('should divide numbers', () => {
expect(divide(6, 2)).toBe(3)
})
it('should throw on divide by zero', () => {
expect(() => divide(6, 0)).toThrow()
})
})
})Test Case
An individual test scenario that validates a specific piece of functionality.
// Single test case
it('should return uppercase text', () => {
const input = 'hello'
const result = toUpperCase(input)
expect(result).toBe('HELLO')
})Test Runner
A tool that executes tests and reports results. Examples: Jest, Vitest, Mocha, Karma.
# Running tests with different test runners
vitest run
jest --coverage
mocha test/**/*.spec.jsCode Coverage
A measurement of how much of your code is executed during tests.
# Generate coverage report
vitest run --coverage
# Output:
# File | % Stmts | % Branch | % Funcs | % Lines |
# ----------|---------|----------|---------|---------|
# utils.ts | 80.00 | 75.00 | 100.0 | 80.00 |Types of coverage:
- Statement: Percentage of statements executed
- Branch: Percentage of branches (if/else) taken
- Function: Percentage of functions called
- Line: Percentage of lines executed
CI/CD Terms
Continuous Integration (CI)
Automated testing that runs whenever code changes are pushed to version control.
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm install
- name: Run tests
run: npm testContinuous Deployment (CD)
Automated deployment to production after all tests pass.
- name: Deploy to production
if: github.ref == 'refs/heads/main' && success()
run: npm run deployTest Doubles
Test Double
A generic term for any object used in place of a real object for testing purposes. Includes mocks, stubs, spies, fakes, and dummies.
| Type | Returns Data | Records Calls | Uses Real Code |
|---|---|---|---|
| Mock | ✅ | ✅ | ❌ |
| Stub | ✅ | ❌ | ❌ |
| Spy | ✅ | ✅ | ✅ |
| Fake | ✅ | ❌ | Simplified |
| Dummy | ❌ | ❌ | ❌ |
Example of a Fake:
// Fake database (in-memory implementation)
class FakeDatabase {
private data: Map<string, any> = new Map()
async save(key: string, value: any) {
this.data.set(key, value)
}
async get(key: string) {
return this.data.get(key)
}
clear() {
this.data.clear()
}
}Test Patterns
AAA Pattern (Arrange-Act-Assert)
A pattern for structuring tests with three distinct phases.
test('should calculate discount', () => {
// Arrange - Set up test data and conditions
const product = { price: 100, category: 'electronics' }
const discountRate = 0.1
// Act - Execute the code being tested
const finalPrice = calculateDiscount(product, discountRate)
// Assert - Verify the results
expect(finalPrice).toBe(90)
})Given-When-Then
BDD-style test structure describing the context, action, and expected outcome.
test('user login flow', () => {
// Given - initial context
const user = { email: 'test@example.com', password: 'password123' }
// When - action occurs
const result = login(user)
// Then - expected outcome
expect(result.success).toBe(true)
expect(result.token).toBeDefined()
})Test Isolation
Tests should run independently without affecting each other.
describe('User service', () => {
// Clean state before each test
beforeEach(async () => {
await cleanDatabase()
})
it('should create user', async () => {
const user = await createUser({ name: 'John' })
expect(user.id).toBeDefined()
})
it('should find user', async () => {
// This test doesn't depend on the previous test
await createUser({ name: 'Jane' })
const user = await findUser('Jane')
expect(user.name).toBe('Jane')
})
})Test Data Builder
A pattern for creating test data with sensible defaults and easy customization.
class UserBuilder {
private user = {
id: 1,
name: 'Test User',
email: 'test@example.com',
role: 'user',
active: true,
}
withName(name: string) {
this.user.name = name
return this
}
withEmail(email: string) {
this.user.email = email
return this
}
asAdmin() {
this.user.role = 'admin'
return this
}
build() {
return { ...this.user }
}
}
// Usage
const admin = new UserBuilder().withName('Admin').asAdmin().build()
const customUser = new UserBuilder()
.withEmail('custom@example.com')
.withName('Custom User')
.build()Test Types and Strategies
Flaky Test
A test that sometimes passes and sometimes fails without any code changes. Often caused by:
- Race conditions
- External dependencies
- Time-based logic
- Improper test isolation
- Resource constraints
// ❌ Flaky test - depends on timing
test('should update after delay', async () => {
triggerUpdate()
await sleep(100) // Might not be enough time!
expect(getValue()).toBe('updated')
})
// ✅ Better - wait for specific condition
test('should update after delay', async () => {
triggerUpdate()
await waitFor(() => expect(getValue()).toBe('updated'))
})Snapshot Testing
Capturing component output and comparing it to a saved snapshot on subsequent runs.
import { render } from '@testing-library/react'
test('UserCard matches snapshot', () => {
const { container } = render(
<UserCard name="John" email="john@example.com" />
)
expect(container).toMatchSnapshot()
})First run creates:
// __snapshots__/UserCard.test.tsx.snap
exports[`UserCard matches snapshot 1`] = `
<div>
<h2>John</h2>
<p>john@example.com</p>
</div>
`;Mutation Testing
Testing the quality of your test suite by introducing small changes (mutations) to the code and checking if tests fail.
// Original code
function add(a, b) {
return a + b
}
// Mutation: change + to -
function add(a, b) {
return a - b // If tests still pass, they're inadequate!
}Mutation testing tools:
- Stryker (JavaScript)
- PIT (Java)
- Mutmut (Python)
Regression
Re-running tests after code changes to ensure new changes didn’t break existing functionality.
// Regression test for bug fix
test('should handle empty input (Bug #1234)', () => {
// This bug occurred when input was empty
const result = processInput('')
// After the fix, empty input should not crash
expect(result).toBe('')
expect(() => processInput('')).not.toThrow()
})Testing Approaches
White Box Testing
Testing with full knowledge of the internal code structure and implementation.
// White box test - knows internal implementation
test('caching mechanism', () => {
const cache = new Cache()
// Test internal cache structure
expect(cache._internalStore.size).toBe(0)
cache.set('key', 'value')
// Verify internal state
expect(cache._internalStore.has('key')).toBe(true)
})Black Box Testing
Testing without knowledge of internal implementation, only testing inputs and outputs.
// Black box test - only tests public API
test('cache stores and retrieves values', () => {
const cache = new Cache()
cache.set('key', 'value')
const result = cache.get('key')
expect(result).toBe('value')
})Gray Box Testing
Testing with partial knowledge of internal structure.
// Gray box test - some knowledge of internals
test('database query optimization', () => {
const result = fetchUsers({ active: true })
// Know it should use index, verify it's efficient
expect(result.queryPlan.usedIndex).toBe(true)
expect(result.executionTime).toBeLessThan(100)
})Acceptance Testing
Testing that validates whether the software meets business requirements and is acceptable for delivery.
describe('Acceptance: Checkout Process', () => {
test('customer can complete purchase', async () => {
// Given customer has items in cart
await addToCart('Product A')
await addToCart('Product B')
// When customer goes through checkout
await goToCheckout()
await enterShippingInfo(shippingAddress)
await enterPaymentInfo(creditCard)
await confirmOrder()
// Then order should be placed successfully
expect(await getOrderConfirmation()).toBeTruthy()
expect(await getConfirmationEmail()).toContain('Order confirmed')
})
})Performance Testing
Performance Testing
Measuring system speed, responsiveness, and stability under workload.
import { performance } from 'perf_hooks'
test('search should complete within 100ms', () => {
const start = performance.now()
const results = search('query')
const duration = performance.now() - start
expect(duration).toBeLessThan(100)
expect(results.length).toBeGreaterThan(0)
})Load Testing
Testing system behavior under expected load conditions.
// k6 load test
import http from 'k6/http'
import { check } from 'k6'
export let options = {
vus: 100, // 100 virtual users
duration: '5m', // for 5 minutes
}
export default function () {
let response = http.get('https://api.example.com/users')
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
})
}Stress Testing
Testing system behavior beyond normal capacity to find breaking points.
export let options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up to 100 users
{ duration: '5m', target: 100 }, // Stay at 100
{ duration: '2m', target: 200 }, // Ramp up to 200
{ duration: '5m', target: 200 }, // Stay at 200
{ duration: '2m', target: 500 }, // Push to 500
{ duration: '2m', target: 0 }, // Ramp down
],
}Security and Accessibility
Security Testing
Finding vulnerabilities and ensuring data protection.
describe('Security Tests', () => {
test('should prevent SQL injection', () => {
const maliciousInput = "'; DROP TABLE users; --"
expect(() => {
queryDatabase(maliciousInput)
}).not.toThrow()
// Database should still exist
expect(checkTableExists('users')).toBe(true)
})
test('should sanitize XSS attempts', () => {
const xssAttempt = '<script>alert("XSS")</script>'
const sanitized = sanitizeInput(xssAttempt)
expect(sanitized).not.toContain('<script>')
})
test('should hash passwords', async () => {
const user = await createUser({ password: 'password123' })
// Password should not be stored in plain text
expect(user.password).not.toBe('password123')
expect(user.password.length).toBeGreaterThan(50) // Hashed
})
})Accessibility Testing
Ensuring software is usable by people with disabilities and meets WCAG guidelines.
import { axe } from 'jest-axe'
test('should have no accessibility violations', async () => {
const { container } = render(<LoginForm />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
test('should be keyboard navigable', () => {
render(<Navigation />)
const firstLink = screen.getAllByRole('link')[0]
firstLink.focus()
expect(document.activeElement).toBe(firstLink)
// Simulate Tab key
userEvent.tab()
// Focus should move to next element
expect(document.activeElement).not.toBe(firstLink)
})Additional Terms
Smoke Test
Quick, basic tests to verify critical functionality works before deeper testing.
Sanity Test
Focused tests on specific functionality after changes.
Happy Path
Testing the expected, successful user flow without errors.
test('happy path: user registration', async () => {
// Everything goes right
await fillRegistrationForm({
email: 'user@example.com',
password: 'ValidPass123!',
confirmPassword: 'ValidPass123!',
})
await submitForm()
expect(screen.getByText(/registration successful/i)).toBeInTheDocument()
})Edge Case
Testing boundary conditions and unusual inputs.
describe('Edge cases', () => {
test('should handle empty array', () => {
expect(sum([])).toBe(0)
})
test('should handle very large numbers', () => {
expect(sum([Number.MAX_SAFE_INTEGER, 1])).toBe(Number.MAX_SAFE_INTEGER + 1)
})
test('should handle special characters in name', () => {
expect(validateName("O'Brien-Smith")).toBe(true)
})
})Test Pyramid
Testing strategy with many fast unit tests, fewer integration tests, and minimal E2E tests.
Test Coverage Metrics
- Statement Coverage: Percentage of statements executed
- Branch Coverage: Percentage of conditional branches taken
- Function Coverage: Percentage of functions called
- Line Coverage: Percentage of code lines executed
- Path Coverage: Percentage of execution paths taken
Test-First Development
Writing tests before implementing features (TDD approach).
Regression Suite
Collection of tests that ensure existing functionality still works.
Integration Point
Where different systems, modules, or components interact.
// Testing integration point between frontend and API
test('user service integrates with API correctly', async () => {
const user = await userService.getUser(1)
// Verify the service correctly calls and processes API response
expect(user).toMatchObject({
id: 1,
name: expect.any(String),
email: expect.any(String),
})
})Quick Reference Table
| Term | Category | Description |
|---|---|---|
| Assertion | Core | Verifies expected behavior |
| Mock | Test Double | Records calls and returns fake data |
| Stub | Test Double | Returns predetermined values |
| Spy | Test Double | Records calls, uses real implementation |
| Fixture | Setup | Fixed test data |
| Test Suite | Organization | Group of related tests |
| Code Coverage | Metric | Measurement of tested code |
| CI/CD | Automation | Continuous integration and deployment |
| Flaky Test | Problem | Inconsistent test results |
| Snapshot | Technique | Comparing output to saved reference |
| Mutation Testing | Quality | Testing the tests themselves |
| Regression | Strategy | Ensuring changes don’t break features |
| White Box | Approach | Testing with internal knowledge |
| Black Box | Approach | Testing only public interface |
| AAA Pattern | Pattern | Arrange-Act-Assert structure |
| Given-When-Then | Pattern | BDD test structure |
| Load Testing | Performance | Testing under expected load |
| Stress Testing | Performance | Testing beyond normal capacity |
| Security Testing | Quality | Finding vulnerabilities |
| Accessibility (A11y) | Quality | Ensuring WCAG compliance |
Next Steps
- Understand Testing Fundamentals
- Learn JavaScript Testing frameworks
- Explore Best Practices for writing maintainable tests