Unit 6 - Notes

INT222

Unit 6: Testing and Deployment

Topic 1: Testing REST API

Testing REST APIs ensures that the backend services function correctly, handle errors gracefully, and maintain data integrity. In advanced web development, this usually involves automated testing frameworks integrated into the development workflow.

1.1 Types of API Testing

  • Unit Testing: Testing individual functions or methods in isolation (e.g., testing a utility function that calculates a total price).
  • Integration Testing: Testing how different modules work together. For APIs, this is the most critical phase, as it tests the HTTP request/response cycle, database interaction, and middleware.
  • End-to-End (E2E) Testing: Simulating a complete user scenario from the frontend through the API to the database and back.
  • Performance/Load Testing: Checking how the API behaves under heavy traffic (Tools: Apache JMeter, k6).

1.2 Key Tools and Frameworks (Node.js Ecosystem)

  • Jest: A comprehensive testing framework by Facebook. It supports assertions, mocking, and code coverage.
  • Mocha: A flexible test runner often paired with assertion libraries.
  • Chai: An assertion library used with Mocha (allows BDD styles like expect or should).
  • Supertest: An HTTP assertion library specifically designed for testing Node.js HTTP servers. It simulates requests to your API without needing the server to be listening on a network port.

1.3 Anatomy of an API Test

When testing a REST API, assertions generally focus on three areas:

  1. HTTP Status Code: Did the server return 200 (OK), 201 (Created), 400 (Bad Request), etc.?
  2. Response Headers: Is the content-type application/json?
  3. Response Body (Payload): Does the JSON data match the expected structure and values?

1.4 Implementation Example (Jest + Supertest)

Scenario: Testing a GET /api/products endpoint.

Step 1: Setup

BASH
npm install --save-dev jest supertest

Step 2: The Test File (products.test.js)

JAVASCRIPT
const request = require('supertest');
const app = require('../app'); // Import your Express app
const mongoose = require('mongoose');

// Setup and Teardown for Database connection
beforeAll(async () => {
    await mongoose.connect(process.env.TEST_DB_URI);
});

afterAll(async () => {
    await mongoose.disconnect();
});

describe('GET /api/products', () => {
    
    it('should return 200 OK and a list of products', async () => {
        const response = await request(app).get('/api/products');
        
        // Assert Status Code
        expect(response.statusCode).toBe(200);
        
        // Assert Headers
        expect(response.headers['content-type']).toEqual(expect.stringContaining('json'));
        
        // Assert Body Structure
        expect(Array.isArray(response.body)).toBeTruthy();
        expect(response.body.length).toBeGreaterThan(0);
        
        // Assert Specific Data Fields
        expect(response.body[0]).toHaveProperty('name');
        expect(response.body[0]).toHaveProperty('price');
    });

    it('should return 404 if route does not exist', async () => {
        const response = await request(app).get('/api/non-existent-route');
        expect(response.statusCode).toBe(404);
    });
});

1.5 Mocking vs. Live Testing

  • Live Testing: Connects to a real test database. Provides the highest confidence but is slower and requires cleanup (database flushing) between tests.
  • Mocking: Replaces the database or external services with "fake" functions.
    • Pros: Fast, deterministic, no network required.
    • Cons: Does not test the actual database query logic.

Topic 2: Deployment with GitHub

Modern deployment relies heavily on Git-based workflows (GitOps). Deployment is no longer manually uploading files via FTP; it is triggered by pushing code to a repository.

2.1 Continuous Integration/Continuous Deployment (CI/CD)

  • CI (Continuous Integration): The practice of automating the integration of code changes from multiple contributors into a single software project. It involves automated building and testing.
  • CD (Continuous Deployment/Delivery): Automatically releasing the code to the production environment after passing CI tests.

2.2 GitHub Actions

GitHub Actions is a CI/CD platform that allows you to automate your build, test, and deployment pipeline directly within GitHub.

Key Concepts:

  • Workflow: A configurable automated process defined by a YAML file in .github/workflows.
  • Event: A specific activity that triggers a workflow (e.g., push, pull_request).
  • Runner: A server (hosted by GitHub or self-hosted) that runs your workflow.
  • Job: A set of steps that execute on the same runner.

2.3 Deployment Strategies using GitHub

A. Static Site Deployment (GitHub Pages)

Best for frontend-only React/Vue/Angular apps or HTML/CSS sites.

  1. Go to Repository Settings > Pages.
  2. Select the branch (usually main or gh-pages) and folder (/ or /docs).
  3. GitHub automatically builds and hosts the site at username.github.io/repo-name.

B. PaaS Deployment (Heroku/Render/Railway)

Connecting a GitHub repo to a Platform as a Service (PaaS) allows for automatic deployment of full-stack (Node.js, Python, etc.) applications.

  • Auto-Deploy: The PaaS detects a push to the main branch.
  • Build Command: The platform runs npm install and npm build.
  • Start Command: The platform runs npm start.

C. Custom Workflow Example (Deploying Node.js to a Server)

This YAML file (.github/workflows/deploy.yml) demonstrates a pipeline that tests the code and then deploys it.

YAML
name: Node.js CI/CD

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18.x'

    - name: Install dependencies
      run: npm ci

    - name: Run Tests
      run: npm test

  deploy:
    needs: build-and-test # Only runs if tests pass
    runs-on: ubuntu-latest
    
    steps:
      - name: Deploy to Production via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            npm install
            pm2 restart app

2.4 Managing Secrets

Never commit .env files to GitHub. Instead, use GitHub Secrets:

  1. Go to Settings > Secrets and variables > Actions.
  2. Add DB_URI, API_KEY, etc.
  3. Access them in workflows using ${{ secrets.SECRET_NAME }}.

Topic 3: Third-Party Rendering

Third-party rendering refers to the architectural pattern where parts of the application's rendering process or content generation are offloaded to external services, Headless CMSs, or specialized rendering engines, rather than being handled entirely by the primary application server or the browser.

3.1 The Rendering Spectrum

To understand third-party rendering, one must understand the standard models:

  • CSR (Client-Side Rendering): Browser downloads empty HTML + JS. JS fetches data and builds the UI (e.g., standard React).
  • SSR (Server-Side Rendering): Server builds HTML per request (e.g., Next.js).

3.2 Headless CMS (Content Rendering)

In this model, the "Third Party" manages the content and provides an API. The rendering logic exists in your app, but the content is rendered from a third-party source.

  • Examples: Contentful, Strapi, Sanity.io.
  • Workflow:
    1. Marketers update content in Contentful (Third Party).
    2. The application (via API) fetches this JSON content during the build process (SSG) or request time (SSR).
    3. The application renders the JSON into HTML components.

3.3 Serverless and Edge Rendering

Using third-party cloud providers (AWS Lambda, Cloudflare Workers, Vercel Edge Functions) to render pages closer to the user.

  • Edge Rendering: Instead of one central server rendering the page, a third-party network (CDN) executes the rendering logic at a node physically closest to the user.
  • Benefit: Drastically reduced latency and Time To First Byte (TTFB).

3.4 Embedded Third-Party Rendering

Sometimes, specific UI components are rendered entirely by a third party and embedded into the application.

  1. iFrames: The classic method. A window into another site. Secure isolation but poor performance and SEO.
    • Use case: Youtube embeds, Google Maps, Payment Gateways (Stripe Elements).
  2. Web Components / Micro-frontends:
    • Different teams or third-party vendors own specific parts of a page (e.g., the Header is rendered by Team A's service, the Product List by Team B's service).
    • These fragments are composed together at runtime.

3.5 Third-Party Rendering Services (BaaS)

Backend-as-a-Service platforms often handle the rendering of authentication UIs or database views.

  • Auth0 / Firebase Auth: When a user logs in, they are often redirected to a page rendered entirely by Auth0, then redirected back with a token. The application offloads the complex rendering of login forms, 2FA, and password resets to the third party.

3.6 Pros and Cons of Third-Party Rendering

Feature Advantage Disadvantage
Development Speed Faster; no need to build CMS or Auth UIs from scratch. Reliance on external documentation.
Maintenance Third party handles security updates and scaling. Vendor lock-in; migration is difficult.
Performance CDNs/Edge rendering is faster than centralized servers. External API latency can block rendering if not handled asynchronously.
Reliability SLAs usually guarantee high uptime. If the third party goes down, that part of your app breaks.