Testing Technologies
The Test Pyramid
The Test Pyramid is a testing strategy that emphasizes having more low-level, fast-running unit tests, fewer integration tests, and even fewer end-to-end tests. The Test Pyramid is a strategy that emphasizes different layers of testing:
Unit Tests – Fast and numerous tests that check small parts of the application (base of the pyramid).
Integration Tests – Fewer than unit tests; check if different modules or services work together.
End-to-End (E2E) Tests – Simulate user flows through the whole system. Expensive to run, so fewer in number.
This structure helps developers balance speed, reliability, and cost of automated testing.
API Testing
Testing helps make sure that software works correctly, securely, and as expected across all parts — backend, frontend, and APIs.
API testing verifies that your application's backend services (REST, GraphQL, gRPC, etc.) behave as expected. API Testing Checks:
Correct responses (status codes, response body)
Error handling (e.g., 400, 404, 500)
Authentication & Authorization
Performance and load handling
Data validation and transformation
Types of API Testing
Unit Testing: Tests individual API functions in isolation.
Types of Unit Tests:
Solitary Unit Test: Uses mocks to isolate dependencies.
Sociable Unit Test: Allows real interactions with some dependencies (e.g., calling another class method).
Example: Testing a calculateDiscount() function using Jest in Node.js.
Integration Testing: Checks how APIs work together or connect with databases.
Example: A product listing API fetching data from MongoDB.
Functional Testing: Checks if the API returns the correct result for given input.
Example: Testing if /cart/add API adds an item correctly.
Security Testing: Ensures unauthorized users can't access secure endpoints.
Example: Checking if admin-only APIs are protected.
Performance Testing: Measures speed, load behavior, and response time.
Tools: JMeter, k6
End-to-End Testing(E2E): Validates complete workflows involving multiple APIs to ensure overall system behavior.
Tools: Cypress
Example: User signs in → adds item to cart → places order.
Regression Testing: Checks that new code changes do not break existing API functionality.
Contract Testing: Ensures communication between services (like frontend and backend) follows a fixed agreement (contract).
If the backend changes its API response format, contract testing will catch the break.
Example: Verifying that the /user/profile API still returns name and email as expected by the frontend.
Tool: Pact(for contract testing)
Types of Contract Testing:
Consumer-Driven Contract Testing: Consumer (client app) defines expectations.
Providers (API service) must meet those.
Reduces risk of breaking changes from the provider.
Tools: Pact, Spring Cloud Contract
Provider-Driven Contract Testing: Provider defines the rules clients must follow.
Often documented in OpenAPI (Swagger) formats.
Tools: Postman, Insomnia, Newman, REST Assured(for Java), SuperTest,Jest(for Node.js testing)
Frontend Testing
Frontend testing validates UI behavior, layout, interactivity, and compatibility across platforms and devices.
Unit Testing: Tests individual components (e.g., buttons, forms)
Example:
Testing if a button shows "Loading.." after being clicked.
Test if a LoginForm validates inputs and calls the login function on submit.
Tool: Jest, Mocha, JUnit (Java), React Testing Library
Integration Testing: Verifies connected components work together
Example: Login form → AuthService → Dashboard redirect
Tool: Postman, Supertest
Visual Regression Testing: Compares screenshots before and after code changes.
Detects unexpected UI changes (e.g., a misplaced button after a CSS tweak).
Helps preserve UI consistency.
Tools: Percy, Chromatic,Storybook
Cross-Browser Testing: Ensures the website looks and works the same on different browsers.
Tools: BrowserStack, Sauce Labs
Example: A button works in Chrome but not in Safari due to an unsupported CSS property.
Acceptance Testing(E2E): Checks if the whole system meets user needs and requirements.
Simulates full user journey
Tool: Cypress, Playwright, Selenium
Example:
User signs in → adds product → checks out
Test if a user can successfully reset their password through the flow defined by the business.
Visual and Layout Consistency Checks: Detects differences in fonts, colors, alignment, and responsive design across browsers.
Tool: Percy, Storybook
Performance Testing: Measures load times and responsiveness on different platforms.
Responsive Testing: Verifies UI adapts to screen sizes
Example: Verifying that a three-column layout collapses into one column on mobile.
Tools: Chrome DevTools (Device Mode), LT Browser
Accessibility Testing (a11y): Ensures usability for disabled users
Example: Ensuring a user can fill and submit a form using only the keyboard.
Tools: axe DevTools, Lighthouse, WAVE, NVDA (screen reader)
Automated API testing using tools like Dredd and Schemathesis
To ensure correct behaviour, We could write unit tests manually for each endpoint, but that takes time and may miss edge cases. Tools like Dredd and Schemathesis automate this process using your API documentation/specification (like an OpenAPI YAML file).
Dredd: Automatically runs tests against REST APIs based on your OpenAPI/Swagger spec.
It ensures that endpoints conform to the documented contract.
Example: Create a simple Express.js API, write its OpenAPI spec, and test it using Dredd.
Workflow:
Reads OpenAPI YAML file
Calls every endpoint
Compares actual response with expected spec
Fails the test if anything doesn’t match
Problem With Default Dredd Behavior: It lacks dynamic behavior. For example:
If an endpoint uses path parameters like /orders/{order_id}, Dredd cannot automatically generate a valid order_id.
It expects hardcoded values like "order_id": "123", which must exist in the system before the test runs.
This leads to brittle tests, heavily dependent on fixtures (preloaded test data).
Dredd Hooks: Hooks allow customizing Dredd’s behavior dynamically during test execution:
Create resources (e.g., POST /orders) at runtime.
Save returned resource IDs.
Reuse those IDs in subsequent API calls (e.g., GET, PUT, DELETE).
Clean up after the test finishes.
Strength: Automatically tests all documented API endpoints (like /orders) using valid ("happy path") payloads.
Limitation: Only tests happy paths, doesn’t handle invalid or edge cases well
Example:
Missing required fields.
Invalid enum values.
Incorrect data types.
To go beyond the happy path, use Schemathesis, which leverages a more exhaustive testing technique called Property-Based Testing.
Schemathesis: It is a Python-based CLI tool. It's language-agnostic in terms of API testing (you can test Node, Java, Python APIs), but the tool itself runs in Python.
Uses property-based testing to test APIs more thoroughly than traditional tools like Dredd.
Internally, it uses the Hypothesis library to generate randomized and edge-case-oriented test cases based on your OpenAPI spec.
Limitation: Requires OpenAPI spec and Python environment
Test-Driven Development (TDD) Cycle
In TDD, we write tests before the code. The cycle:
Write a test: Based on a feature or requirement.
Run the test: It should fail (red phase).
Write minimal code: Just enough to pass the test.
Run tests again: Should pass (green phase).
Refactor: Clean up code without changing functionality.
Four Phases of a Test
Setup – Prepare input or environment
Exercise – Call the function/method
Verify – Assert the expected output
Teardown – Clean up (e.g., close DB, delete temp files)
Code Example:
let user; beforeEach(() => { user = { name: 'Alice', age: 25 }; // Setup }); test('updates user age correctly', () => { updateUserAge(user, 30); // Exercise expect(user.age).toBe(30); // Verify }); afterEach(() => { user = null; // Teardown });
Test Coverage
Test coverage tells how much of your code is tested. It Measures:
What lines, functions, and branches are tested.
What parts of the code are not touched by any test.
Helps identify untested or dead code.
Ensures critical paths in the application are verified.
Helps we write more complete tests
More coverage = more confidence in your code.
Example in Node.js
Math.js
// math.js function add(a, b) { return a + b; } function unusedFunction() { return 42; }
math.test.js
// math.test.js test('adds numbers', () => { expect(add(2, 3)).toBe(5); });
Only add() is covered, not unusedFunction(). That’s why we don’t get 100% coverage.
Test Doubles (Simulating External Dependencies)
Test Doubles are fake objects used in testing to simulate real ones like DBs or APIs.
Types of Test Doubles:
Test Spy: Tracks if a function was called, how often, and with what arguments
Does not change actual behavior
Example: Spy on emailService.send() to check usage
Test Stub: Replaces actual behavior with predefined output
Prevents real API/DB access
Example: Stub DB call to return fake user data
Mock: A full fake object that simulates an external service.
Mocks both behavior and interaction. Useful when:
We want to simulate a service (like sending an email)
AND check if the service was used properly (method called, arguments correct)
Mock Example: Sending a Welcome Email
Real code:
function sendWelcomeEmail(user, emailService) { emailService.send(user.email, 'Welcome!'); }
Mock Test:
const mockEmailService = { send: jest.fn() }; const user = { email: 'manish@example.com' }; sendWelcomeEmail(user, mockEmailService); // Simulate and verify expect(mockEmailService.send).toHaveBeenCalledWith('manish@example.com', 'Welcome!');
Jest – JavaScript Testing Framework
Jest is a powerful testing framework especially popular in React and Node.js projects.
Components of Jest:
Test Runner: Finds and runs test files.
Assertion Library: Validates test expectations (expect()).
Mocking Framework: Mocks external dependencies.
Mocks help simulate APIs, databases, or services so that unit tests can run in isolation.
Example 1:
describe('Login Function', () => { test('returns true for correct password', () => { expect(checkPassword('1234')).toBe(true); }); });
Example 2:
describe('Addition', () => { test('adds numbers correctly', () => { expect(1 + 2).toBe(3); }); });
describe() groups tests.
test() or it() defines individual test cases.
expect() asserts expected behavior.
React Testing Library (RTL)
A lightweight library for testing React components.
Key Concepts:
RTL focuses on testing React components the way users interact with them, not how they’re implemented.
Focuses on behavior, not implementation.
RTL Features:
Queries by role, text, or label: Simulates real user behavior.
Encourages better accessibility because it interacts with accessible labels.
Encourages accessible markup (labels, roles).
Avoids testing implementation details (e.g., internal state).
Example:
const loginBtn = screen.getByRole('button', { name: /login/i });
fireEvent.click(loginBtn);
Last updated