diff --git a/absolute-beginners/backend-beginner/testing/_category_.json b/absolute-beginners/backend-beginner/testing/_category_.json new file mode 100644 index 0000000..1f616cf --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Testing", + "position": 10, + "link": { + "type": "generated-index", + "description": "Don't just write code, write reliable code. Learn the different levels of testing to ensure your backend is bug-free and production-ready." + } +} \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/functional-testing.mdx b/absolute-beginners/backend-beginner/testing/functional-testing.mdx new file mode 100644 index 0000000..dcba07c --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/functional-testing.mdx @@ -0,0 +1,93 @@ +--- +sidebar_position: 4 +title: "Functional & E2E Testing" +sidebar_label: "4. Functional Testing" +description: "Learn how to test your API endpoints from the outside-in to ensure the business logic works for the user." +--- + +Functional testing (often called **End-to-End** or **Black Box** testing) doesn't care about your clean code, your design patterns, or your variable names. It only cares about one thing: **"Does the feature actually work for the user?"** + +In the **CodeHarborHub** backend, this usually means sending a real HTTP request to your API and checking if you get the correct HTTP response. + +## The "Black Box" Concept + +Imagine your API is a black box. You can't see inside it. +1. You push a button (Send a `POST` request to `/api/register`). +2. Something happens inside. +3. You check the result (Did I get a `201 Created` status and a Welcome email?). + +## Functional vs. Unit Testing + +| Feature | Unit Testing | Functional Testing | +| :--- | :--- | :--- | +| **Viewpoint** | Developer (White Box) | User (Black Box) | +| **Goal** | Correctness of logic | Correctness of feature | +| **Example** | Testing the `sum()` function | Testing the `Checkout` process | +| **Dependencies** | Mocked (Fake) | Real (Server + DB) | + +## Tools for Functional Testing + +To test your API endpoints without opening a browser or using Postman manually, we use **Supertest**. It allows us to "simulate" HTTP requests inside our Jest tests. + +### Example: Testing the Signup Endpoint + +```javascript +import request from 'supertest'; +import app from '../app'; // Your Express app +import { prisma } from '../lib/prisma'; + +describe('POST /api/auth/signup', () => { + + test('should create a new user and return 201', async () => { + // 1. Send the request + const response = await request(app) + .post('/api/auth/signup') + .send({ + name: 'Ajay Dhangar', + email: 'test@codeharborhub.com', + password: 'securePassword123' + }); + + // 2. Assert the HTTP Status + expect(response.status).toBe(201); + + // 3. Assert the Response Body + expect(response.body).toHaveProperty('id'); + expect(response.body.name).toBe('Ajay Dhangar'); + + // 4. Verification: Is it actually in the DB? + const userInDb = await prisma.user.findUnique({ + where: { email: 'test@codeharborhub.com' } + }); + expect(userInDb).not.toBeNull(); + }); + + test('should return 400 if email is missing', async () => { + const response = await request(app) + .post('/api/auth/signup') + .send({ name: 'Ajay' }); + + expect(response.status).toBe(400); + expect(response.body.message).toMatch(/required/); + }); +}); +``` + +## The "Happy Path" vs. "Edge Cases" + +In functional testing at **CodeHarborHub**, you must test both: + +1. **The Happy Path:** Everything goes perfectly (User enters correct data, server is up). +2. **The Sad Path:** The user makes a mistake (Invalid email, password too short). +3. **The Edge Case:** What happens if a user tries to register with an email that already exists? + +## Summary Checklist + + * [x] I understand that Functional Testing is "Black Box" testing. + * [x] I know that Functional Tests check the API from the user's perspective. + * [x] I can use **Supertest** to simulate HTTP requests. + * [x] I understand the importance of testing "Sad Paths" and "Edge Cases." + +:::info Best Practice +Functional tests are slower than unit tests because they start the entire server and talk to the database. Run them **after** your unit tests have passed to catch "big picture" bugs before you deploy to production! +::: \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/integration-testing.mdx b/absolute-beginners/backend-beginner/testing/integration-testing.mdx new file mode 100644 index 0000000..275f869 --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/integration-testing.mdx @@ -0,0 +1,95 @@ +--- +sidebar_position: 3 +title: Integration Testing +sidebar_label: "3. Integration Testing" +description: "Learn how to test the interaction between different modules, such as your code and the database." +--- + +While Unit Tests prove that a single brick is strong, **Integration Tests** prove that the mortar (the glue) holds the bricks together to form a wall. + +In a typical **CodeHarborHub** backend, this means testing if your **Service Layer** can successfully talk to your **Database** or an **External API**. + + +## 🧐 The "Why" Behind Integration Tests + +You might have a perfectly working `User` object and a perfectly working `Database`. But if the User object expects a `firstName` and the Database table is named `first_name`, your app will crash. + +**Unit tests won't catch this. Integration tests will.** + +## What Are We Testing? + +In integration testing, we move beyond simple logic and start testing the "edges" of our application: + +1. **Database Integration:** Does my query actually return data from PostgreSQL? +2. **API Integration:** Does my app correctly parse the JSON response from a payment gateway? +3. **File System:** Can my app successfully write a PDF report to the `/uploads` folder? + +## Setting Up the Environment + +Because integration tests touch real systems, they are slower and more complex than unit tests. Here is the professional workflow we use: + + + + **Never** run integration tests against your "Production" or "Development" database. + 1. Create a separate `test_db`. + 2. Run **Migrations** to set up the schema. + 3. Seed the database with "dummy" data. + 4. Wipe the data after the tests finish. + + + Use a `.env.test` file to point your app to the test database instead of the real one. + + + +## Example: Testing a User Service + +Let's test if our `UserService` can actually save a user into the database using **Prisma**. + +```javascript +import { UserService } from '../services/userService'; +import { prisma } from '../lib/prisma'; + +describe('UserService Integration', () => { + + // Clean up the database before each test + beforeEach(async () => { + await prisma.user.deleteMany(); + }); + + test('should successfully create a user in the database', async () => { + const userService = new UserService(); + const userData = { name: 'Ajay', email: 'ajay@codeharborhub.com' }; + + // Act: Call the service that talks to the DB + const newUser = await userService.createUser(userData); + + // Assert: Check if it's in the real DB + const dbUser = await prisma.user.findUnique({ + where: { email: 'ajay@codeharborhub.com' } + }); + + expect(dbUser).toBeDefined(); + expect(dbUser.name).toBe('Ajay'); + }); +}); +``` + +## Unit vs. Integration + +| Feature | Unit Testing | Integration Testing | +| :--- | :--- | :--- | +| **Scope** | One function | Multiple modules | +| **Dependencies** | Mocked (Fake) | Real (DB, APIs) | +| **Speed** | Milliseconds | Seconds | +| **Debugging** | Easy (Know exactly where) | Harder (Could be the DB, Config, or Code) | + +## Summary Checklist + + * [x] I understand that integration tests check the "interaction" between modules. + * [x] I know that I should use a dedicated **Test Database**. + * [x] I understand that integration tests catch bugs that unit tests miss (like schema mismatches). + * [x] I know how to use `beforeEach` to keep my test database clean. + +:::warning Don't Overdo It! +Because integration tests are slower, don't try to test every single "if/else" condition here. Use **Unit Tests** for the logic and **Integration Tests** just to ensure the connection works! +::: \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/intro-to-testing.mdx b/absolute-beginners/backend-beginner/testing/intro-to-testing.mdx new file mode 100644 index 0000000..f496b67 --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/intro-to-testing.mdx @@ -0,0 +1,67 @@ +--- +sidebar_position: 1 +title: Introduction to Testing +sidebar_label: "1. Why We Test" +description: "Understand the mindset of software testing and why it is the most important habit of a professional developer." +--- + +Imagine you are building a bridge. You wouldn't wait until the bridge is finished to see if it can hold a car, right? You would test every bolt, every beam, and every cable **during** the build. + +In Software Engineering, **Testing** is the process of verifying that your code behaves exactly as you intended. At **CodeHarborHub**, we follow one simple rule: **"If it's not tested, it's already broken."** + +## 🧐 The "Confidence" Factor + +Why do we spend 30% of our time writing tests? + +1. **Fearless Refactoring:** Want to change your code to make it cleaner? If you have tests, you'll know instantly if you broke something. +2. **Documentation:** A test tells other developers (and your future self) exactly how a function is supposed to work. +3. **Cost Savings:** Finding a bug while coding costs **`$1`**. Finding that same bug after it's live costs **`$1,000`** in lost users and emergency fixes. + +## The Testing Pyramid + +Not all tests are created equal. A professional strategy looks like a pyramid: + +### 1. Unit Tests (The Base) +These test the smallest "units" of code (like a single function). +* **Speed:** ⚑ Lightning fast (thousands per second). +* **Cost:** πŸ’° Very cheap to write. +* **Example:** Testing if a `validateEmail()` function returns `false` for `"invalid-email"`. + +### 2. Integration Tests (The Middle) +These test how different parts of your app work together. +* **Speed:** 🐒 Slower (requires a database or an API). +* **Focus:** Does the `User Service` correctly save a user to the `Database`? + +### 3. E2E / Functional Tests (The Top) +These test the entire "End-to-End" journey of a user. +* **Speed:** 🐌 Very slow (simulates a real browser/user). +* **Example:** "A user signs up, receives a welcome email, and can log in." + +## Manual vs. Automated Testing + +At **CodeHarborHub**, we move away from manual clicking and toward **Automated Scripts**. + +| Feature | Manual Testing | Automated Testing | +| :--- | :--- | :--- | +| **Execution** | Human-driven (Slow) | Machine-driven (Fast) | +| **Reliability** | Prone to human error | Consistent every time | +| **Cost** | High (Time = Money) | Low (Initial setup only) | +| **Regression** | Hard to repeat | Runs on every "Git Push" | + +## The Developer's Toolbox + +To start testing in the Node.js ecosystem, you will encounter these terms: + +* **Test Runner:** The engine that finds and runs your tests (e.g., **Jest**, **Vitest**, **Mocha**). +* **Assertion Library:** The language used to define success (e.g., `expect(result).toBe(true)`). +* **Mocks/Stubs:** "Fake" versions of real services (like a fake Payment Gateway) so you don't spend real money during tests. + +## Summary Checklist +* [x] I understand that testing provides a "Safety Net" for my code. +* [x] I can explain why Unit Tests are the foundation of the pyramid. +* [x] I know the difference between Manual and Automated testing. +* [x] I understand that catching bugs early saves time and money. + +:::tip Mindset Shift +Don't think of testing as "finding bugs." Think of it as **defining requirements**. If the test passes, your requirement is met. If it fails, your code hasn't finished its job yet. +::: \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/mocking-and-stubs.mdx b/absolute-beginners/backend-beginner/testing/mocking-and-stubs.mdx new file mode 100644 index 0000000..879f7a6 --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/mocking-and-stubs.mdx @@ -0,0 +1,88 @@ +--- +sidebar_position: 6 +title: "Mocking & Stubs" +sidebar_label: "6. Mocking & Stubs" +description: "Learn how to fake external dependencies like APIs and Databases to keep your tests fast and reliable." +--- + +In a real-world application like **CodeHarborHub**, your code doesn't live in a bubble. It talks to: +* πŸ“§ Email Services (SendGrid/Nodemailer) +* πŸ’³ Payment Gateways (Stripe/Razorpay) +* ☁️ Cloud Storage (AWS S3) +* 🌐 External APIs (GitHub/Google) + +If you use the **real** services during testing, your tests will be slow, they might cost you money, and they will fail if the internet goes down. We solve this by using **Mocks** and **Stubs**. + +## 🧐 What’s the Difference? + +While people often use these terms interchangeably, there is a technical difference: + +| Concept | Simple Definition | Analogy | +| :--- | :--- | :--- | +| **Stub** | A "dumb" object that returns a hardcoded value. | A pre-recorded voicemail message. | +| **Mock** | A "smart" object that records *how* it was called. | A spy who reports back: "The target called me twice at 5:00 PM." | + +## When to use Mocking? + +You should mock any dependency that is **non-deterministic** (unpredictable) or **external**: + +1. **Network Requests:** Don't hit a real URL; mock the response. +2. **Time:** If a feature only works on weekends, "mock" the system clock to be a Saturday. +3. **Randomness:** If a function generates a random ID, mock it to always return `123`. +4. **Costly Actions:** Mocking the "Send Email" function so you don't spam real users during testing. + +## Mocking with Jest + +Let's say we have a function that sends a "Course Completion" email to a student. + +### The Service + +```javascript title="emailService.js" +export const sendWelcomeEmail = async (email) => { + // Imagine this calls a real API like SendGrid + const response = await fetch('[https://api.sendgrid.com/v3/send](https://api.sendgrid.com/v3/send)', { ... }); + return response.ok; +}; +``` + +### The Test +We want to test our `signup` logic without actually sending an email. + +```javascript title="auth.test.js" +import * as emailService from './emailService'; +import { signupUser } from './auth'; + +// 1. Tell Jest to "hijack" the email service +jest.mock('./emailService'); + +test('signup should call the email service', async () => { + // 2. Setup the "Mock" to return a successful value + emailService.sendWelcomeEmail.mockResolvedValue(true); + + const result = await signupUser('ajay@example.com'); + + // 3. Assert: Check if the function was CALLED + expect(emailService.sendWelcomeEmail).toHaveBeenCalledTimes(1); + expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('ajay@example.com'); + expect(result.success).toBe(true); +}); +``` + +## The Dangers of Over-Mocking + +Mocking is powerful, but if you mock **everything**, your tests become useless. + +* **Bad:** Mocking your own internal database logic in an *Integration Test*. (You want to test the real DB!) +* **Good:** Mocking the Stripe API in a *Unit Test*. (You don't want to charge a real card!) + +> **Rule of Thumb:** Mock the things you don't control (3rd party APIs). Don't mock the things you do control (your own logic). + +## Summary Checklist +* [x] I understand that Mocks replace "Real-world" unpredictable services. +* [x] I know that **Stubs** provide data, while **Mocks** verify behavior. +* [x] I can use `jest.mock()` to fake a module. +* [x] I understand that over-mocking can lead to tests that pass even when the app is broken. + +:::success πŸŽ‰ Testing Module Complete! +Congratulations! You've learned how to build a professional testing suite. From **Unit Tests** to **Mocks**, you now have the tools to build a robust, industrial-level backend for **CodeHarborHub**. +::: \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/tdd-approach.mdx b/absolute-beginners/backend-beginner/testing/tdd-approach.mdx new file mode 100644 index 0000000..045ac9d --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/tdd-approach.mdx @@ -0,0 +1,96 @@ +--- +sidebar_position: 5 +title: "Test-Driven Development (TDD)" +sidebar_label: "5. TDD Approach" +description: "Learn the Red-Green-Refactor cycle to write cleaner, more reliable code by writing tests first." +--- + +Most developers write code like this: **Write Code β†’ Run App β†’ Fix Bugs.** In TDD, we flip that on its head: **Write Test β†’ Watch it Fail β†’ Write Code to Pass.** + +This might feel slow at first, but it prevents 90% of the bugs that usually crawl into your codebase during the "Fixing" stage. + +## The Red-Green-Refactor Cycle + +This is the heartbeat of TDD. You repeat this cycle for every small feature you build. + +### 1. RED: Write a Failing Test +Before you write a single line of application logic, you write a test for a feature that doesn't exist yet. +* **Why?** It defines exactly what your code *should* do. +* **Result:** The test fails because the function is missing or empty. + +### 2. GREEN: Make it Pass +Write the **minimum** amount of code required to make the test pass. Don't worry about "clean code" yetβ€”just make the light turn green. +* **Why?** It keeps you focused on the specific goal. +* **Result:** The test passes. + +### 3. REFACTOR: Clean it Up +Now that you have a "Safety Net" (the passing test), you can clean up your code. Rename variables, remove duplication, and optimize. +* **Why?** It ensures your code stays professional and maintainable. +* **Result:** The code is beautiful, and the test **still passes**. + +## TDD in Action: A Password Validator + +Let's build a password validator for **CodeHarborHub** using TDD. + +### Step 1: RED (The Test) +We want a function that returns `false` if the password is less than 8 characters. + +```javascript title="validator.test.js" +import { isSecure } from './validator'; + +test('should return false if password is less than 8 characters', () => { + expect(isSecure('12345')).toBe(false); // This fails because isSecure doesn't exist! +}); +``` + +### Step 2: GREEN (The Code) + +We write just enough code to pass. + +```javascript title="validator.js" +export const isSecure = (password) => { + return password.length >= 8; +}; +``` + +*Run the test: **Passed!*** + +### Step 3: REFACTOR + +Maybe we want to rename the variable for better clarity. + +```javascript +export const isSecure = (pwd) => { + const MIN_LENGTH = 8; + return pwd.length >= MIN_LENGTH; +}; +``` + +*Run the test again: **Still Passed!*** + +## Why use TDD at CodeHarborHub? + +| Feature | Without TDD | With TDD | +| :--- | :--- | :--- | +| **Bugs** | Found late (by users) | Found early (by you) | +| **Code Quality** | Messy (hard to fix later) | Clean (Refactored safely) | +| **Requirements** | Vague | Crystal Clear (The Tests) | +| **Confidence** | "I think it works..." | "I know it works\!" | + +## When NOT to use TDD? + +While TDD is amazing for core logic (like auth, payments, and math), it's not always the best for: + + * **UI/CSS:** Hard to test "if it looks pretty" with a script. + * **Prototyping:** If you are just "exploring" an idea and don't know what the output should be yet. + +## Summary Checklist + + * [x] I understand the **Red-Green-Refactor** cycle. + * [x] I know that TDD means writing the test *before* the code. + * [x] I understand that TDD acts as a blueprint for my logic. + * [x] I know that "Refactor" means cleaning code without changing its behavior. + +:::info Professional Advice +TDD feels like "extra work" for the first week. But after your first giant project where you change 100 lines of code and **know** it's still safe because of your tests, you'll never go back! +::: \ No newline at end of file diff --git a/absolute-beginners/backend-beginner/testing/unit-testing.mdx b/absolute-beginners/backend-beginner/testing/unit-testing.mdx new file mode 100644 index 0000000..544addb --- /dev/null +++ b/absolute-beginners/backend-beginner/testing/unit-testing.mdx @@ -0,0 +1,83 @@ +--- +sidebar_position: 2 +title: Unit Testing +sidebar_label: "2. Unit Testing" +description: "Learn how to test the smallest components of your code in isolation using Jest." +--- + +A **Unit Test** focuses on the smallest possible part of your application usually a single function, method, or class. The goal is to prove that a specific input always produces the expected output, without worrying about the database, the internet, or other files. + +## The Core Principles (FIRST) + +To write professional-grade unit tests at **CodeHarborHub**, your tests should follow the **FIRST** principles: + +* **Fast:** Tests should run in milliseconds. You should be able to run hundreds of them every time you save a file. +* **Independent:** One test should not depend on the result of another. +* **Repeatable:** A test should pass or fail the same way every time, regardless of the environment. +* **Self-Validating:** The test should clearly report "Pass" or "Fail" without you having to check a log. +* **Thorough/Timely:** Cover edge cases (like empty strings or negative numbers), not just the "happy path." + +## The Anatomy of a Test (AAA Pattern) + +Every good unit test follows the **AAA** structure. This keeps your test code organized and readable. + +1. **Arrange:** Set up the data and conditions needed for the test. +2. **Act:** Call the function or method you are testing. +3. **Assert:** Check if the result matches your expectation. + +## Your First Test with Jest + +Let's say we have a utility function that calculates a discount for our **CodeHarborHub** courses. + +### The Function +```javascript title="mathUtils.js" +export const calculateDiscount = (price, percentage) => { + if (percentage < 0 || percentage > 100) return price; + return price - (price * (percentage / 100)); +}; +``` + +### The Test + +```javascript title="mathUtils.test.js" +import { calculateDiscount } from './mathUtils'; + +describe('calculateDiscount()', () => { + + test('should apply a 20% discount correctly', () => { + // 1. Arrange + const price = 100; + const discount = 20; + + // 2. Act + const result = calculateDiscount(price, discount); + + // 3. Assert + expect(result).toBe(80); + }); + + test('should return original price if discount is invalid', () => { + const result = calculateDiscount(100, 150); + expect(result).toBe(100); + }); +}); +``` + +## Why Is Isolation Important? + +Imagine you are testing a function that formats a user's name. If that function also tries to connect to a database to fetch the name, and the database is down, your test fails. + +**Is the name-formatter broken?** We don't know! + +By **Unit Testing**, we remove the database. We give the function a "hardcoded" name and check if it formats it correctly. This ensures that when a test fails, we know exactly which line of code is at fault. + +## Summary Checklist + + * [x] I understand that a Unit Test only tests one small function. + * [x] I can explain the **Arrange, Act, Assert** (AAA) pattern. + * [x] I know that unit tests must be fast and independent. + * [x] I understand that unit tests do not talk to databases or external APIs. + +:::info Pro-Tip +In the **CodeHarborHub** curriculum, we use **Jest**. It is the most popular testing framework for JavaScript and comes with everything you need: a test runner, an assertion library, and mocking tools. +::: \ No newline at end of file