Phase 12: Testing
- Authors

- Name
- Wan Ilhami
- @wan-ilhami-43515a184
The Absolute Beginner's Guide to Automated Testing
Imagine you are a tightrope walker. When you are practicing three feet off the ground, you don't need a safety net. If you fall, you just stand back up. But what if you are walking a tightrope between two skyscrapers? Without a safety net, every step is terrifying. A single mistake is fatal.
Writing software without automated tests is like walking between skyscrapers without a net.
When your codebase is small, you can manually click through your app to make sure everything works. But when your app has 500 features and 50,000 lines of code, changing a tiny function in the "User Authentication" module might accidentally break the "Shopping Cart" module. You won't notice until a customer complains.
Automated testing is writing code to test your code. It is your safety net. It gives you the confidence to refactor, upgrade, and add features fearlessly.
1. The Testing Pyramid
You shouldn't write tests randomly. The industry standard is the Testing Pyramid, which tells you what kind of tests to write and in what proportion.
The Bottom Layer: Unit Tests (Fast & Plentiful)
A unit test verifies the smallest piece of code possible in complete isolation—usually a single function or a single class.
- Characteristics: Extremely fast (running in milliseconds). You should have hundreds or thousands of them.
- Analogy: Testing a single spark plug before putting it into a car engine.
- Rule: Unit tests should never connect to a real database or a real network API. They must be pure logic tests.
The Middle Layer: Integration Tests (Slower & Fewer)
An integration test checks how multiple pieces of your application work together.
- Characteristics: Slower than unit tests because they often involve connecting to a test database or writing to a file system.
- Analogy: Putting the spark plug, pistons, and fuel line together and making sure the engine runs.
- Example: Testing a "Create User" route handler that receives an HTTP request and successfully inserts a record into a PostgreSQL database.
The Top Layer: End-to-End (E2E) Tests (Slowest & Fewest)
E2E testing simulates a real human using your application. It boots up a real browser, clicks real buttons, types into real forms, and verifies the final output.
- Characteristics: Very slow and brittle (prone to breaking if you change the UI slightly). You should only write E2E tests for the most critical user flows (like the checkout process).
- Analogy: Putting the engine in the car, putting a driver in the seat, and taking it for a test drive on the highway.
- Tools: Cypress, Playwright, Selenium.
2. Your First Unit Test (Using Jest)
Let's look at how to write a unit test. We will use Jest, the most popular testing framework for JavaScript.
Imagine we have a simple function that calculates the total price of a shopping cart, including tax.
// cart.js
function calculateTotal(items, taxRate) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal + (subtotal * taxRate);
}
module.exports = { calculateTotal };
How do we test this? We create a file named cart.test.js. A test consists of three phases, commonly known as Arrange, Act, and Assert.
// cart.test.js
const { calculateTotal } = require('./cart');
// 'test' is a globally available function in Jest
test('calculates total with 10% tax correctly', () => {
// 1. ARRANGE: Set up the data we need for the test
const myItems = [
{ name: 'Apple', price: 10 },
{ name: 'Banana', price: 20 }
];
const tax = 0.10; // 10%
// 2. ACT: Execute the function we are testing
const result = calculateTotal(myItems, tax);
// 3. ASSERT: Check if the result matches our expectations
// Subtotal is 30. 10% of 30 is 3. Total should be 33.
expect(result).toBe(33);
});
When you run Jest in your terminal, it will print a beautiful green checkmark: ✓ calculates total with 10% tax correctly.
3. The Magic of Mocking
We established that Unit Tests should never talk to a real database or external API. But what if the function we want to test depends on a database?
Answer: We use Mocks.
A Mock is a "Stunt Double" for your code. Just like a famous actor uses a stunt double for dangerous scenes, our function uses a fake, mocked object instead of a real database connection.
// userService.js
// This function relies on a Database object
async function getUserGreeting(db, userId) {
const user = await db.findById(userId); // This makes a slow network call!
return `Hello, ${user.name}!`;
}
// userService.test.js
test('returns a greeting for the user', async () => {
// Create a FAKE database object (A Mock)
const mockDatabase = {
// We force the mock to return exactly what we want instantly
findById: async (id) => {
return { id: 1, name: 'Alice' };
}
};
// We pass the fake database into our function
const greeting = await getUserGreeting(mockDatabase, 1);
// Assert
expect(greeting).toBe('Hello, Alice!');
});
Because of the mock, our test runs in 1 millisecond and doesn't require a running database!
4. Test-Driven Development (TDD)
TDD is a software engineering philosophy that completely flips the coding process upside down. Instead of writing code and then writing tests, you write the test before the code exists.
The TDD cycle is known as Red, Green, Refactor:
- Red: Write a test for a feature you haven't built yet. Run the test. It will fail (showing red text in the terminal) because the function doesn't exist.
- Green: Write the absolute minimum amount of ugly, messy code required to make the test pass (turn green).
- Refactor: Now that you have a safety net (a passing test), you can rewrite your messy code to make it clean, beautiful, and architecturally sound. If you make a mistake while refactoring, the test will instantly turn red, catching your error.
Summary
Testing feels like a chore when you first learn it. It feels like you are doing double the work to achieve the same result. But the first time an automated test catches a catastrophic bug seconds before you deploy to production, you will understand. Tests are not extra work; they are an investment that pays immense dividends in speed, stability, and peace of mind.