Skip to content
Back to articles
ci-cdtestinggithub-actionsvibe-codingdevops

Tests, CI/CD, and GitHub Actions: What Vibe Coders Need to Understand

Green and red dots on GitHub seem simple, but they hide a system that can save your project. Understand how tests, CI/CD, and GitHub Actions work together.

8 min read

Tests, CI/CD, and GitHub Actions: What Vibe Coders Need to Understand

Green dots on GitHub don't mean your code works. They mean the tests you wrote passed. The difference is enormous.


Context

You're building an app with Claude Code. You push to GitHub and, a few minutes later, a green or red dot appears on the repository. Green, you move on. Red, something broke.

This dynamic seems simple, but it hides three distinct concepts working together: automated tests, CI/CD, and GitHub Actions. Understanding each one separately (and how they connect) is the difference between having a real safety net and having just the illusion of one.

What Are Automated Tests

An automated test is a piece of code that verifies whether another piece of code works as expected. Nothing more.

Consider a function that formats duration in seconds to MM:SS format:

function formatDuration(seconds: number): string {
  const mins = Math.floor(seconds / 60)
  const secs = seconds % 60
  return `${mins}:${secs.toString().padStart(2, '0')}`
}

The corresponding test verifies specific scenarios:

import { describe, it, expect } from 'vitest'
import { formatDuration } from './utils'

describe('formatDuration', () => {
  it('should format seconds as MM:SS', () => {
    expect(formatDuration(845)).toBe('14:05')
    expect(formatDuration(60)).toBe('1:00')
    expect(formatDuration(0)).toBe('0:00')
  })

  it('should pad seconds with zero', () => {
    expect(formatDuration(65)).toBe('1:05')
    expect(formatDuration(3)).toBe('0:03')
  })
})

The test calls the function with known inputs and compares the result with the expected value. If formatDuration(845) returns anything other than "14:05", the test fails.

Types of Tests

Tests are categorized by the scope of what they verify:

Type What it tests Example
Unit A single function or module in isolation formatDuration(845) returns "14:05"
Integration Multiple modules working together Reading MDX files returns ordered modules with valid frontmatter
E2E (end-to-end) The entire app, from browser to database Clicking "Buy" redirects to checkout with the correct amount

The classic recommendation is the "testing pyramid": many unit tests (fast and cheap), some integration tests (middle ground), and few E2E tests (slow and brittle). In practice, what matters is that tests cover what actually matters to the business.

What to Actually Test

Here's the trap few people mention: it's easy to write tests for trivial things and ignore what actually matters.

A test that verifies a text formatting function works is useful, but it doesn't protect against problems that cause real damage. Consider an app that processes payments and serves paid content. The most valuable tests would be:

  • Does the payment webhook process the transaction and grant access correctly?
  • Does the authentication middleware block unauthorized users?
  • Does the routing system deliver the right content to the right domain?

Writing 50 tests for utility functions and zero for the payment flow is like installing an alarm in the garage and leaving the front door open.

What is CI/CD

CI/CD consists of two complementary practices, usually implemented together:

CI (Continuous Integration) is the practice of frequently integrating code into a shared repository, with each integration verified by an automated build that includes tests. Martin Fowler, who popularized the term, defines it as: "a software development practice where each member of a team merges their changes into a codebase together with their colleagues' changes at least daily. Each of these integrations is verified by an automated build (including test) to detect integration errors as quickly as possible."

CD (Continuous Delivery/Deployment) extends CI by taking validated code all the way to production:

  • Continuous Delivery: validated code is ready for deploy, but someone approves manually.
  • Continuous Deployment: validated code goes automatically to production without human intervention.

In practice, for most vibe coding projects, the flow looks like:

push to GitHub → CI runs (lint + typecheck + tests) → merge to main → CD deploys automatically

Organizations that adopt CI/CD deploy 208 times more frequently with 106 times faster lead times, according to Google's DORA (DevOps Research and Assessment) report.

Tests versus CI/CD: What's the Difference?

Tests are the content. CI/CD is the mechanism.

Tests are the scripts that verify whether code works. CI/CD is the system that runs those scripts automatically at specific moments (on every push, on every pull request, before every deploy).

Without tests, CI can check formatting and types, but it doesn't know if the app works. Without CI, tests exist but depend on someone remembering to run them. One doesn't replace the other.

Concept What it is On its own
Tests Code that verifies other code Useful, but depends on manual discipline
CI Automation that runs tests on every push Mechanism without substance if it lacks real tests
CD Automation that deploys after CI passes Dangerous if CI doesn't test what matters

What is GitHub Actions

GitHub Actions is the CI/CD platform built into GitHub. It lets you automate any development workflow directly in the repository.

A workflow is defined in a YAML file inside .github/workflows/. Each workflow contains:

  • Trigger: the event that starts execution (push, pull request, schedule)
  • Jobs: work blocks that run on virtual machines
  • Steps: sequential commands within each job

Anatomy of a Real CI

A typical ci.yml file for a Next.js project:

name: CI
on:
  push:
    branches: [develop, main]
  pull_request:
    branches: [develop, main]

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm eslint .

      - name: Format
        run: pnpm prettier --check .

      - name: Typecheck
        run: pnpm tsc --noEmit

      - name: Test
        run: pnpm vitest run

Every push to develop or main triggers this workflow. It installs dependencies, runs the linter (checks code style), Prettier (checks formatting), the TypeScript compiler (checks types), and finally the tests.

If any step fails, the entire workflow fails. That's the red dot.

What About Deploy?

Deploy usually lives in a separate workflow (deploy.yml) that runs only when code reaches the production branch:

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        run: |
          ssh server "cd /app && git pull && docker compose up -d --build"

This is CD in action: as soon as CI passes and the merge to main happens, deploy is automatic.

The Green Dot Trap

Here's the critical point vibe coders need to understand: the green dot only means that what you configured to be checked passed. If CI only checks lint and types, the green dot only confirms the code is formatted and types are correct. It doesn't confirm the app works.

Consider this real scenario:

  1. CI runs: ESLint, Prettier, TypeScript, and unit tests
  2. All pass. Green dot
  3. Deploy happens automatically
  4. In production, checkout breaks because a Server Action changed the payload structure

CI did everything it was configured to do. The problem is that nobody configured a test for the checkout flow.

The Safety Net Has Holes

An honest audit of any project reveals the same thing: existing tests rarely cover what generates revenue. It's easier (and faster) to write a test for a formatting function than to simulate a payment webhook. So trivial tests accumulate and critical tests are left for "later."

The result is a CI/CD infrastructure set up correctly, but validating the wrong content. The machinery works. It's the content on the conveyor belt that's incomplete.

Self-assessment checklist:

  • Does the flow that generates revenue (payment, checkout) have at least one test?
  • Does the authentication system have tests?
  • Does CI run build in addition to typecheck? (typecheck doesn't catch all build errors)
  • Is there at least one E2E test for the critical path?
  • Is minimum coverage defined and enforced in CI?

If the answer to most is "no," the green dot is giving a false sense of security.

In Practice

Step by step to set up tests and CI in a Next.js project with Vitest:

1. Install Vitest and test dependencies

pnpm add -D vitest @vitejs/plugin-react @testing-library/react

2. Configure vitest.config.ts

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'node',
    coverage: {
      provider: 'v8',
      thresholds: { lines: 60 }
    }
  },
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') }
  }
})

3. Write your first test

Create a file src/lib/__tests__/example.test.ts:

import { describe, it, expect } from 'vitest'

describe('my function', () => {
  it('should return the expected result', () => {
    const result = myFunction('input')
    expect(result).toBe('expected output')
  })
})

4. Add scripts to package.json

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage"
  }
}

5. Create the CI workflow

Create .github/workflows/ci.yml with the content from the "Anatomy of a Real CI" section above.

6. Push and observe

git add . && git commit -m "feat: add CI with tests" && git push

Open the Actions tab on GitHub and watch the execution.

Tip: Start by testing the most critical path in your app (what generates revenue or what would break the user experience). A single test on the payment flow is worth more than 20 tests on utility functions.

What This Means for Vibe Coding

Vibe coding with AI assistants makes it trivial to generate code quickly. This is both a superpower and a risk. The faster you generate code, the faster you can introduce bugs that only surface in production.

Tests and CI/CD aren't bureaucracy for big companies. They're the mechanism that lets you move fast without breaking what already works. The green dot on GitHub isn't decoration. It's concrete evidence that specific checks passed.

But evidence only has value if the checks are relevant. Setting up CI without tests on critical paths is like having a fire alarm that only detects smoke in the kitchen. Better than nothing. But it doesn't replace detectors in the rest of the house.

Next time you see a green dot, ask: "what exactly is this checking?" The answer might be more revealing than the color.


References