Verdict
Yes
Yes, run one smoke test in CI against the production build: serve the built output, open each top-level route in a headless browser and fail on any script error; keep the unit tests on the source.
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilitysame-day
- Reversibilitytrivial
- Test costmoderate
Yes, test the production build minimally, with one smoke test of the built output. The typical case is a single-page web app whose bundler minifies and splits the code only in the production build, while unit tests run against the source. Blast radius is users, because customers load that bundle, and Change frequency is regularly, because a bundler upgrade or a build config edit changes the output about once a month. Detectability is same-day, because a blank page or a chunk that fails to load reaches error reports within hours. Reversibility is trivial, since a redeploy serves the previous bundle, and Test cost is moderate: the test builds the app, serves it and opens pages in a headless browser.
| When | Decision | Why |
|---|---|---|
| The build output is a library on npm that other projects import | Test minimally: before each release, install the npm pack tarball in a fixture project and import every entry point from ESM and CommonJS | Reversibility moves to with-effort, because npm keeps the broken version and consumers must install a patch; the decision holds because consumers hit the import error at once and report it |
| Saved documents store constructor.name as the type of each shape | Test: against the production build, save a document and assert each shape's type, and give each class a fixed type string | Detectability moves to eventually and Reversibility to with-effort, because minified names save without an error and old documents need a repair script |
| The bundle holds the checkout, and a script error only in the production build stops orders | Test mandatory: before each release, an end-to-end test places an order against the production build in the payment provider's test mode | Blast radius rises to money and Reversibility to costly, because orders lost while the checkout was broken stay lost |
| A static site of a few pages, where each pull request gets a preview deployment that its author opens before merge | Do not add a smoke test; open every page of the preview before merge | Detectability moves to immediately, because a broken page shows on the preview in front of the author of the change |
| The build serves a content dashboard that only staff use | Do not test the build; staff report a broken page, and a redeploy of the previous bundle fixes it | Blast radius falls to internal, because no customer sees the failure |
What breaks if you don't test
Unit tests import the source, so they never run the code that the minifier and the tree shaker produce. Every unit test passes when a wrong base path sends lazy chunks to a 404, or when webpack drops a CSS import from a package that declares sideEffects: false. Customers get a blank or unstyled page after the deploy, and the first report comes from a user or the error tracker.
What you lose if you over-test
Running the whole unit suite against the minified bundle breaks every test that imports an internal module, because the bundle exports only its entry points. A long end-to-end suite on the production build repeats what the unit tests already checked, and each of its tests breaks when a label or a route changes.
How to test
Keep the unit tests on the source, and add one CI job after them. Serve the built output with vite preview, Vite's static server for dist, or any static file server, and let Playwright's webServer option start it. Open each top-level route, fail on any pageerror or failed chunk request, and check one element per page.
When the answer changes
- The build output is a package that other projects install.
- Code reads class or function names at runtime.
- Every pull request gets a preview deployment that its author opens.
Real incident + Code example
The shapes that changed after a release
On a diagram editor I worked on, saved documents stored this.constructor.name as each shape's type. Unit tests and the dev server ran unminified code, so every check saw Rectangle. The production build renamed the classes, as MDN warns minifiers do, and documents saved in production stored types such as t. In the next release the minifier gave those letters to other classes, and old documents opened with the wrong shapes. A user reported it eleven days later, and we wrote a repair script from the previous build's source map. We gave each class a fixed static type and added this test, which Playwright runs against the production build:
import { test, expect } from '@playwright/test';
test('production build saves shape types by name', async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', (error) => errors.push(error.message));
await page.route('**/api/documents', (route) => route.fulfill({ status: 201 }));
await page.goto('/editor/new');
await page.getByRole('button', { name: 'Rectangle' }).click();
const [save] = await Promise.all([
page.waitForRequest('**/api/documents'),
page.getByRole('button', { name: 'Save' }).click(),
]);
expect(save.postDataJSON().shapes[0].type).toBe('Rectangle');
expect(errors).toEqual([]);
});
The assertion fails on the minified build that stored t, and on the dev server it passes with or without the fix.
Related questions
FAQ
- Should I test a minified production build?
Yes, run one smoke test in CI against the minified production build, and keep the unit tests on the source. Minification renames functions and classes, so only a test of the built output sees failures that the renaming causes.
- In CI, should tests be run against src or dist?
Run the unit tests against src, and run one smoke test against dist after the build. Unit tests on src reach internal modules, while the smoke test checks the files you deploy.
- Should /dist/ be included in Jest tests?
No, add
<rootDir>/dist/to Jest'stestPathIgnorePatterns. When the compiler writes test files into dist, Jest otherwise runs each test twice. Test the built output with a separate smoke test.