Using Git Hooks to Automate Linting and Testing

Team 4 min read

#git

#automation

#linting

#testing

Overview

Automating linting and testing at the git level helps catch issues before they enter your main branches. By running ESLint (or your preferred linter) and your test suite on commits and pushes, you ensure code quality stays high without manual overhead. This guide walks you through a practical setup using Husky, a popular tool for managing Git hooks in Node.js projects.

Prerequisites

  • A Node.js project with ESLint configured and a test runner (e.g., Jest, Vitest).
  • Basic familiarity with npm or yarn.
  • A working git repository.

Why use Git Hooks for linting and testing

  • Early feedback: Fail fast on bad code or failing tests.
  • Consistency: Every contributor runs the same checks automatically.
  • Reduced review burden: Reviewers see cleaner diffs and fewer obvious issues.

Setting up Husky

Husky makes it straightforward to manage Git hooks in your project. Follow these steps to enable pre-commit and pre-push hooks that run linting and tests.

  • Install Husky as a development dependency:

    • npm: npm install husky —save-dev
    • yarn: yarn add husky —dev
  • Install hooks (one-time):

    • npm: npx husky install
    • yarn: npx husky install
    • Optional: add a prepare script so hooks install automatically when dependencies are installed:
      • npm: add “prepare”: “husky install” to your package.json
      • yarn: same approach
  • Add the hooks:

    • Pre-commit hook to run lint and tests:
      • npx husky add .husky/pre-commit “npm run lint && npm test”
    • Pre-push hook to run tests (safer to guard the push as well):
      • npx husky add .husky/pre-push “npm test”
  • Example shell commands to set up:

    • npx husky install
    • npx husky add .husky/pre-commit “npm run lint && npm test”
    • npx husky add .husky/pre-push “npm test”

Configure npm scripts

In your package.json, define the lint and test scripts (and prepare for Husky if you want automatic hook installation):

  • package.json example:
    • {
    • “name”: “my-project”,
    • “scripts”: {
    • "prepare": "husky install",
    • "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    • "test": "jest"
    • }
    • }

Notes:

  • Adjust lint command to match your ESLint setup (include TypeScript extensions if needed).
  • If you use another test runner (Vitest, Mocha, etc.), replace “jest” accordingly.

Example: pre-commit and pre-push hooks

  • Pre-commit runs lint and tests:
    • npx husky add .husky/pre-commit “npm run lint && npm test”
  • Pre-push runs tests only (optional):
    • npx husky add .husky/pre-push “npm test”

What happens on commit:

  • If linting fails, the commit is aborted and you’ll see lint output.
  • If tests fail, the commit is aborted and you’ll see test output.
  • If both pass, the commit proceeds.

What happens on push:

  • The push will fail if tests fail, preventing bad code from reaching remote branches.

Example configurations

  • ESLint setup (sample ESLint config path):
    • ESLint runs on all JS/TS files in the project with the specified extensions.
  • Jest (or Vitest) tests:
    • Ensure your test script runs quickly for a better git hook experience.

Code blocks for reference:

  • package.json (snippets)

    • {
    • “name”: “my-project”,
    • “scripts”: {
    • "prepare": "husky install",
    • "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    • "test": "jest"
    • }
    • }
  • Husky hook commands (one-liners)

    • npx husky add .husky/pre-commit “npm run lint && npm test”
    • npx husky add .husky/pre-push “npm test”

Advanced tips and gotchas

  • Skipping hooks for a commit:
    • Use an environment variable to bypass hooks if needed:
      • HUSKY_SKIP_HOOKS=1 git commit -m “quick commit”
    • Or use git commit -n to skip hooks for that commit.
  • Performance considerations:
    • If linting or tests are slow, consider running a targeted subset (lint only on changed files with lint-staged, or run tests selectively).
  • Monorepos:
    • For large mono repos, you may want per-package hooks or conditional logic to only run appropriate scripts.
  • Consistency across environments:
    • Commit hooks depend on your local environment. Document the setup steps in CONTRIBUTING.md to help new contributors.

Troubleshooting

  • Hooks not running after install:
    • Ensure the .husky directory exists and is committed, or run npx husky install again.
    • Check that the prepare script runs on install so hooks are installed automatically.
  • ESLint or test failures:
    • Run the commands locally outside of git hooks to verify issues are reproducible.
    • Consider adding more descriptive error messages in your lint/test scripts if needed.

Real-world usage and next steps

  • Start with a minimal setup (lint + test on pre-commit) and iterate.
  • Consider integrating with CI for an extra safety net, but rely on hooks for immediate feedback during development.
  • If your project evolves, revisit which checks run on hooks to balance quality with developer velocity.

Conclusion

Git hooks are a lightweight, developer-friendly way to keep code quality in check. By automating linting and testing with Husky and npm scripts, you get fast feedback at the very moment of code changes, reducing the risk of introducing issues into your main branches.