How to test staged Git changes without stashing

A local test can pass even when the staged files are missing a dependency. In this example, staged checkout.cjs imports discount.cjs, which exists on disk and remains untracked. A clean checkout of the resulting commit would fail with MODULE_NOT_FOUND.

git checkout-index copies the staging area, or index, into a temporary directory. Running the tests there checks the file versions that a plain git commit would record. Git’s guide to staging files

Run the test on a copy of the index

Run git status --short and git diff --cached from the repository root. The first-column M means the change to checkout.cjs is staged, and ?? marks the untracked helper. The staged diff adds a call to require("./discount.cjs"). Git status documentation · Git diff documentation

M  checkout.cjs
?? discount.cjs

The test passes in the working directory because both files are present.

node --test checkout.test.cjs
✔ applies a checkout discount

Define the function in Bash at the repository root. The subshell keeps the temporary directory change local to the test.

test_staged_checkout() (
  snapshot=$(mktemp -d) || exit 1
  trap 'rm -r -- "$snapshot"' EXIT
  git checkout-index --all --prefix="$snapshot/" || exit 1
  cd "$snapshot" || exit 1
  node --test checkout.test.cjs
)

test_staged_checkout

Keep the trailing slash in --prefix="$snapshot/" so Git writes the files inside that directory. --all includes unchanged tracked files such as checkout.test.cjs alongside staged changes. Run this on a regular, full checkout with any merge conflicts resolved. Git checkout-index documentation

The test now reports Error: Cannot find module './discount.cjs' with code MODULE_NOT_FOUND. Node resolves that relative path next to checkout.cjs in the temporary directory. Git copied the staged version of checkout.cjs, and there is no discount.cjs in the index yet. Node.js documentation on file modules

Stage the missing helper and rerun

Review the helper and stage that file. Each call to test_staged_checkout makes a fresh copy, so it picks up the newly staged file.

git add -- discount.cjs
git diff --cached --stat
test_staged_checkout

The test now passes in the export. This fixture uses built-in Node modules. For an npm project with a committed lockfile, run npm ci && npm test inside the export to use its dependency files. npm ci documentation

The export contains source files without the repository’s .git metadata. Builds that read Git history or use submodules or Git LFS may need additional setup or a full checkout.

The example was verified with Git 2.50.1 and Node.js 24.13.0. For the basic Git workflow, see How to Use VSCode, WSL, and GitHub for Coding.

Scratch repository for reproducing the failure

The following Bash commands create the same starting point in a temporary repository. After they finish, run the tests and the staged-copy commands above from that directory.

scratch=$(mktemp -d) && bash -eu -s -- "$scratch" <<'SETUP'
cd "$1"
git init -q
echo 'module.exports = (total) => total - 10;' > checkout.cjs
cat > checkout.test.cjs <<'EOF'
const test = require("node:test");
const assert = require("node:assert/strict");
const checkout = require("./checkout.cjs");

test("applies a checkout discount", () => assert.equal(checkout(100), 90));
EOF
git add -- checkout.cjs checkout.test.cjs
git -c user.name=Demo -c [email protected] commit -qm 'Add checkout'

cp checkout.cjs discount.cjs
echo 'module.exports = require("./discount.cjs");' > checkout.cjs
git add -- checkout.cjs
SETUP

Run cd "$scratch" after setup succeeds.