TestingTesting Terminology

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 recorded

Fixture

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.js

Code 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 test

Continuous Deployment (CD)

Automated deployment to production after all tests pass.

- name: Deploy to production
  if: github.ref == 'refs/heads/main' && success()
  run: npm run deploy

Test 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.

TypeReturns DataRecords CallsUses Real Code
Mock
Stub
Spy
FakeSimplified
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

TermCategoryDescription
AssertionCoreVerifies expected behavior
MockTest DoubleRecords calls and returns fake data
StubTest DoubleReturns predetermined values
SpyTest DoubleRecords calls, uses real implementation
FixtureSetupFixed test data
Test SuiteOrganizationGroup of related tests
Code CoverageMetricMeasurement of tested code
CI/CDAutomationContinuous integration and deployment
Flaky TestProblemInconsistent test results
SnapshotTechniqueComparing output to saved reference
Mutation TestingQualityTesting the tests themselves
RegressionStrategyEnsuring changes don’t break features
White BoxApproachTesting with internal knowledge
Black BoxApproachTesting only public interface
AAA PatternPatternArrange-Act-Assert structure
Given-When-ThenPatternBDD test structure
Load TestingPerformanceTesting under expected load
Stress TestingPerformanceTesting beyond normal capacity
Security TestingQualityFinding vulnerabilities
Accessibility (A11y)QualityEnsuring WCAG compliance

Next Steps