Skip to main content

Testing Types

Understand the different types of software testing and when to use each approach for maximum effectiveness. Each testing type serves a specific purpose in your testing strategy.

Unit Testing​

What is Unit Testing?​

Unit testing validates individual units of code (functions, methods, classes) in complete isolation from external dependencies.

Characteristics​

  • Fast - Runs in milliseconds
  • Isolated - No external dependencies
  • Focused - Tests one thing at a time
  • Simple - Easy to write and maintain
  • Reliable - Deterministic results

When to Use​

Test:

  • Business logic and algorithms
  • Data transformations
  • Validation functions
  • Utility functions
  • Edge cases and error handling

Example:

function calculateDiscount(price, discountPercent) {
if (price < 0 || discountPercent < 0 || discountPercent > 100) {
throw new Error('Invalid input');
}
return price * (1 - discountPercent / 100);
}

// Unit test
test('calculateDiscount should apply percentage discount', () => {
expect(calculateDiscount(100, 10)).toBe(90);
});

test('calculateDiscount should throw error for negative price', () => {
expect(() => calculateDiscount(-100, 10)).toThrow('Invalid input');
});

Best Practices​

✅ Test behavior, not implementation ✅ One assertion per test (when possible) ✅ Use descriptive test names ✅ Test edge cases and error conditions ✅ Keep tests fast (< 1ms each)


Integration Testing​

What is Integration Testing?​

Integration testing validates how multiple components work together, including interactions with external systems like databases, APIs, and file systems.

Characteristics​

  • Moderate speed - Runs in seconds
  • Real dependencies - Uses actual databases, APIs
  • Component interaction - Tests multiple units together
  • More complex - Requires setup and teardown
  • More realistic - Closer to production behavior

When to Use​

Test:

  • API endpoints
  • Database operations (CRUD)
  • Service-to-service communication
  • Third-party integrations
  • Authentication and authorization
  • Message queues and event handlers

Example:

// Integration test with real database
describe('UserService', () => {
let database;

beforeAll(async () => {
database = await setupTestDatabase();
});

afterAll(async () => {
await database.close();
});

test('should create user and save to database', async () => {
const userData = {
email: 'test@example.com',
name: 'Test User'
};

const user = await userService.create(userData);

// Verify it was saved
const savedUser = await database.users.findById(user.id);
expect(savedUser.email).toBe('test@example.com');
expect(savedUser.name).toBe('Test User');
});
});

Best Practices​

✅ Use test databases (not production!) ✅ Clean up data after each test ✅ Test realistic scenarios ✅ Mock external services when needed ✅ Keep tests independent


End-to-End (E2E) Testing​

What is E2E Testing?​

End-to-End testing validates complete user workflows from start to finish, testing the entire application stack as a user would experience it.

Characteristics​

  • Slow - Runs in seconds to minutes
  • Real browser - Uses actual browsers (Chrome, Firefox)
  • Full stack - Tests UI, backend, database together
  • Brittle - More likely to break
  • Expensive - Time-consuming to write and maintain

When to Use​

Test:

  • Critical user paths (login, checkout, signup)
  • Complete workflows (create order, submit form)
  • Cross-browser compatibility
  • Visual regression
  • Smoke tests before deployment

Example:

// E2E test with Playwright
test('user can complete checkout process', async ({ page }) => {
// Navigate to site
await page.goto('https://example.com');

// Add item to cart
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="cart-icon"]');

// Proceed to checkout
await page.click('[data-testid="checkout-button"]');

// Fill shipping info
await page.fill('#email', 'user@example.com');
await page.fill('#address', '123 Main St');

// Complete payment
await page.fill('#card-number', '4242424242424242');
await page.click('[data-testid="place-order"]');

// Verify success
await expect(page.locator('.success-message')).toBeVisible();
await expect(page).toHaveURL(/\/order-confirmation/);
});

Best Practices​

✅ Test only critical paths ✅ Keep tests stable and reliable ✅ Use data-testid attributes ✅ Run in parallel when possible ✅ Use page object model for reusability


Acceptance Testing​

What is Acceptance Testing?​

Acceptance testing verifies that software meets business requirements and is acceptable to stakeholders. Often written in a format non-technical stakeholders can understand.

Characteristics​

  • Business-focused - Tests requirements and specifications
  • Stakeholder-readable - Often using BDD syntax
  • High-level - Tests features, not implementation
  • Automated or manual - Can be either

When to Use​

Test:

  • User stories and requirements
  • Business rules
  • Feature acceptance criteria
  • Compliance requirements

Example (BDD style):

Feature: User Login
As a user
I want to log in to my account
So that I can access my personal dashboard

Scenario: Successful login with valid credentials
Given I am on the login page
When I enter valid email "user@example.com"
And I enter valid password "SecurePass123"
And I click the "Login" button
Then I should see my dashboard
And I should see a welcome message with my name

Scenario: Failed login with invalid password
Given I am on the login page
When I enter valid email "user@example.com"
And I enter invalid password "wrongpassword"
And I click the "Login" button
Then I should see an error message "Invalid credentials"
And I should remain on the login page

Performance Testing​

What is Performance Testing?​

Performance testing evaluates how the system performs under various conditions, including load, stress, and scalability testing.

Types of Performance Tests​

Load Testing:

  • Test system under expected load
  • Verify response times meet requirements
  • Identify bottlenecks

Stress Testing:

  • Test system beyond normal capacity
  • Find breaking points
  • Test recovery mechanisms

Spike Testing:

  • Sudden increase in load
  • Test auto-scaling
  • Verify system stability

Endurance Testing:

  • Sustained load over time
  • Memory leaks
  • Resource exhaustion

When to Use​

Test:

  • API response times
  • Database query performance
  • Page load times
  • Concurrent user capacity
  • Resource usage under load

Example:

// Performance test with k6
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
stages: [
{ duration: '30s', target: 50 }, // Ramp up to 50 users
{ duration: '1m', target: 50 }, // Stay at 50 users
{ duration: '30s', target: 100 }, // Ramp up to 100 users
{ duration: '1m', target: 100 }, // Stay at 100 users
{ duration: '30s', target: 0 }, // Ramp down
],
};

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,
});

sleep(1);
}

Security Testing​

What is Security Testing?​

Security testing identifies vulnerabilities and ensures the application is protected against threats.

Types of Security Tests​

Vulnerability Scanning:

  • Automated scanning for known vulnerabilities
  • Dependency checks
  • Configuration issues

Penetration Testing:

  • Ethical hacking
  • Simulate real attacks
  • Find security weaknesses

Authentication Testing:

  • Password strength
  • Session management
  • OAuth flows

Authorization Testing:

  • Access control
  • Role-based permissions
  • Data isolation

Common Security Tests​

describe('Security Tests', () => {
test('should prevent SQL injection', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/api/users/search')
.send({ query: maliciousInput });

expect(response.status).toBe(400);
// Verify database still exists
const users = await database.users.count();
expect(users).toBeGreaterThan(0);
});

test('should prevent XSS attacks', async () => {
const xssPayload = '<script>alert("XSS")</script>';
const response = await request(app)
.post('/api/comments')
.send({ content: xssPayload });

const savedComment = await database.comments.findById(response.body.id);
expect(savedComment.content).not.toContain('<script>');
});

test('should require authentication for protected routes', async () => {
const response = await request(app)
.get('/api/user/profile');

expect(response.status).toBe(401);
});
});

Smoke Testing​

What is Smoke Testing?​

Smoke testing (or sanity testing) performs a quick check to verify that the critical functionality works before more thorough testing.

Characteristics​

  • Quick - Takes minutes
  • High-level - Tests basic functionality only
  • Go/No-Go - Determines if further testing is worthwhile
  • Automated - Typically runs in CI/CD pipeline

When to Use​

Test:

  • After deployment
  • After builds
  • Before full test suite
  • Critical paths only

Example:

describe('Smoke Tests', () => {
test('application starts successfully', async () => {
const response = await fetch('http://localhost:3000/health');
expect(response.status).toBe(200);
});

test('database connection works', async () => {
const result = await database.query('SELECT 1');
expect(result).toBeDefined();
});

test('API returns expected structure', async () => {
const response = await request(app).get('/api/users/1');
expect(response.body).toHaveProperty('id');
expect(response.body).toHaveProperty('email');
});

test('authentication service is available', async () => {
const response = await fetch('http://auth-service/health');
expect(response.status).toBe(200);
});
});

Regression Testing​

What is Regression Testing?​

Regression testing ensures that new changes haven't broken existing functionality. Typically involves re-running existing tests.

When to Use​

Test:

  • After bug fixes
  • After feature additions
  • Before releases
  • After refactoring

Best Practices​

✅ Automate regression tests ✅ Run in CI/CD pipeline ✅ Prioritize critical paths ✅ Use version control for tests ✅ Track test coverage


Choosing the Right Test Type​

Decision Matrix​

NeedTest Type
Test business logicUnit Tests
Test API endpointsIntegration Tests
Test user workflowsE2E Tests
Verify requirementsAcceptance Tests
Check performanceLoad/Stress Tests
Find vulnerabilitiesSecurity Tests
Quick health checkSmoke Tests
Prevent regressionsRegression Tests

Testing Strategy​

Start with:

  1. Unit tests for core logic (70%)
  2. Integration tests for APIs (20%)
  3. E2E tests for critical paths (10%)

Add as needed:

  • Performance tests for scalability concerns
  • Security tests for sensitive data
  • Smoke tests for deployment verification

Resources​

Next Steps​

  1. Read Testing Basics for fundamentals
  2. Learn Test-Driven Development methodology
  3. Explore Test Doubles (mocks, stubs, spies)
  4. Apply to your language/framework

Master different test types to build a comprehensive testing strategy!