Testing Strategies
Routing misconfigurations are the most common microfrontends problem. A wrong path pattern can send users to 404 pages. Tests catch these before deployment.
Outcome
Write routing validation tests using @vercel/microfrontends/next/testing utilities.
The Problem: Configuration Drift
As your application grows, routing changes:
- New paths added to apps
proxy.tsmatchers updated- Redirects added or removed
Without tests, these changes can break routing silently. A redirect in marketing that conflicts with a docs path? You won't know until users complain.
Testing Utilities
The @vercel/microfrontends package includes three test utilities:
| Utility | Purpose |
|---|---|
validateRouting | Verify paths route to correct apps |
validateMiddlewareConfig | Ensure proxy.ts matchers work with routing |
validateMiddlewareOnFlaggedPaths | Test feature flag routing behavior |
Fast Track
- Install Jest in the marketing app
- Write routing validation tests
- Add a
proxy.tsand test its matcher - Run tests in CI
Hands-on Exercise 3.2
Add routing tests to the Acme Platform.
Part 1: Set Up Jest
Add Jest to the marketing app (where microfrontends.json lives):
cd apps/marketing
pnpm add -D jest @types/jest ts-jestCreate apps/marketing/jest.config.js:
/** @type {import('jest').Config} */
const config = {
testEnvironment: "node",
transform: {
"^.+\\.tsx?$": ["ts-jest", { useESM: true }],
},
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
},
testMatch: ["**/__tests__/**/*.test.ts"],
};
module.exports = config;Add test script to apps/marketing/package.json:
{
"scripts": {
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start",
"lint": "eslint",
"test": "jest"
}
}Part 2: Write Routing Tests
Create apps/marketing/__tests__/routing.test.ts:
/* @jest-environment node */
import { validateRouting } from "@vercel/microfrontends/next/testing";
describe("microfrontends routing", () => {
const configPath = "./microfrontends.json";
test("routes marketing paths to marketing app", () => {
expect(() =>
validateRouting(configPath, {
"acme-marketing": ["/", "/pricing", "/about", "/contact"],
})
).not.toThrow();
});
test("routes docs paths to docs app", () => {
expect(() =>
validateRouting(configPath, {
"acme-docs": [
"/docs",
"/docs/getting-started",
"/docs/api",
"/docs/api/reference",
"/docs/guides/setup",
],
})
).not.toThrow();
});
test("routes dashboard paths to dashboard app", () => {
expect(() =>
validateRouting(configPath, {
"acme-dashboard": [
"/app",
"/app/projects",
"/app/projects/123",
"/settings",
"/settings/profile",
"/settings/billing",
],
})
).not.toThrow();
});
test("404 paths fall through to default app", () => {
expect(() =>
validateRouting(configPath, {
"acme-marketing": ["/unknown-page", "/random/nested/path"],
})
).not.toThrow();
});
});The keys are the application keys from microfrontends.json. A typo like "marketing" fails with Application marketing does not exist in the microfrontends config, which is a nice way to find out.
Part 3: Add a Proxy and Test Its Matcher
The marketing app doesn't have a proxy.ts yet. Give it one that runs the microfrontends middleware. With no flags it leaves routing alone for now, and Lesson 4.1 adds a flag to it.
import type { NextRequest } from "next/server";
import { runMicrofrontendsMiddleware } from "@vercel/microfrontends/next/middleware";
export async function proxy(request: NextRequest) {
const response = await runMicrofrontendsMiddleware({
request,
flagValues: {},
});
if (response) {
return response;
}
}
export const config = {
matcher: ["/.well-known/vercel/microfrontends/client-config"],
};The package still says "middleware" in its function names. They work in Next.js 16's proxy.ts. The matcher includes /.well-known/vercel/microfrontends/client-config because the browser asks that endpoint which app serves a flagged path.
Create apps/marketing/__tests__/proxy.test.ts:
/* @jest-environment node */
import { validateMiddlewareConfig } from "@vercel/microfrontends/next/testing";
import { config as proxyConfig } from "../proxy";
describe("proxy configuration", () => {
const configPath = "./microfrontends.json";
test("marketing proxy config is compatible with microfrontends", () => {
expect(() =>
validateMiddlewareConfig(proxyConfig, configPath)
).not.toThrow();
});
});This test fails if the matcher catches a path that belongs to a child app, like /docs. Those requests never reach the default app's proxy, so a matcher that claims them is lying to you. It also fails if the client-config path is missing.
Part 4: Run the Tests
cd apps/marketing
pnpm testExpected output:
PASS __tests__/routing.test.ts
microfrontends routing
✓ routes marketing paths to marketing app (15 ms)
✓ routes docs paths to docs app (3 ms)
✓ routes dashboard paths to dashboard app (2 ms)
✓ 404 paths fall through to default app (2 ms)
PASS __tests__/proxy.test.ts
proxy configuration
✓ marketing proxy config is compatible with microfrontends (8 ms)
Test Suites: 2 passed, 2 total
Tests: 5 passed, 5 total
Catching Misconfigurations
What happens when routing is wrong?
Add a failing test to see the error:
test("EXAMPLE: this would catch a misconfiguration", () => {
expect(() =>
validateRouting("./microfrontends.json", {
// Wrong! /docs should route to acme-docs, not acme-marketing
"acme-marketing": ["/docs"],
})
).toThrow();
});Flip .toThrow() to .not.toThrow() and Jest prints exactly what's wrong:
Incorrect microfrontends routing detected:
- Expected /docs to match acme-marketing, but it matched acme-docs (on /docs, /docs/:path*).
Adding Tests to CI
Update turbo.json to include tests:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^lint"]
}
}
}Add root-level test script to package.json:
{
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"test": "turbo test",
"lint": "turbo lint"
}
}Now pnpm test runs tests across all apps.
Commit
git add -A
git commit -m "test: add microfrontends routing validation tests"Done-When
- Jest configured in marketing app
- Routing tests pass for all three apps
proxy.tsconfiguration test passes- Tests can run via
pnpm testfrom root
Test Coverage Strategy
| What to Test | Why |
|---|---|
| All known paths | Ensure routing doesn't break |
| Nested paths | Verify wildcard patterns work |
| Edge cases (404s) | Confirm fallback behavior |
proxy.ts matchers | Prevent matcher conflicts |
| Feature flag paths | Validate conditional routing |
What's Next
Debug headers and tracing for observing routing decisions in production.
Was this helpful?