一、什么是 SSR 测试?
想象一下,你的蛋糕店现在开通了外卖服务。
- 传统的客户端渲染(CSR) :顾客点外卖后,你送过去的是一个材料包和一叠食谱 (发送 JS 包)。顾客收到后,得自己在厨房(浏览器)里看食谱、搅拌、烘烤(执行 JS),最后才能吃到蛋糕(看到页面)。缺点:顾客要等很久才能吃到(首屏加载慢),而且如果他的厨房没有烤箱(JS 执行失败),蛋糕就做不出来。
- 服务端渲染(SSR) :为了解决这个问题,你搞了个"中央厨房"。顾客点单后,中央厨房(Node.js 服务器)直接帮你把蛋糕做好、装盒 (渲染出完整的 HTML 页面),然后配送出去。顾客收到的是立刻能吃的成品蛋糕(完整的 HTML)。之后,再把"食谱"(JS 包)附上,这样顾客如果想自己加颗草莓(交互),就能按食谱操作(客户端激活 Hydration)。
SSR 测试 就是用来检验你这个"中央厨房"的:
- 能不能做:给你一个订单(请求),你能不能正确地做出对应的蛋糕(生成正确的 HTML)?
- 做得好不好:做出来的蛋糕形态、口味对不对(HTML 结构、内容是否正确)?
- 配送后体验:附上的食谱(JS)能不能让顾客在收到成品后顺利地进行二次加工(Hydration 是否成功,能否接管交互)?
SSR 测试的核心是验证同一套 Vue 代码在 Node.js 服务器环境和浏览器客户端环境下协同工作的正确性。
二、Vue 2 SSR 测试的运行机制(流程图)
官方仓库的 SSR 测试跑的是 Jasmine ,Hydration 测试跑的是 Karma+Mocha;Jest 只是社区常见写法。
Vue 2 的 SSR 测试核心是 "同构" 验证,即同一套组件代码在服务器和客户端运行的结果应该是一致的。
以下是 Vue2 SSR 测试的完整流程图:

- 构建测试包
- 工具:Rollup(官方仓库)。
- 产物:
--vue-server-renderer/build.dev.js
/build.prod.js
(完整版,支持流式渲染、BundleRenderer)。
--vue-server-renderer/basic.js
(极简版,仅renderVueComponentToString
)。
• 注意:这些产物仅用于「运行时」或「SSR 测试」,并非给浏览器用的 UMD。
- 服务端渲染测试 (Server Test)
-
工具:官方用 Jasmine (
npm run test:ssr
);社区可用 Jest /Mocha /Vitest。 -
环境:真正的 Node.js,无 JSDOM。
-
步骤:
- 引入
createRenderer
(或renderToString
)和待测组件。 - 调用
renderToString(vm)
得到纯 HTML 字符串。 - 断言:字符串包含期望的 tag / class / data-attribute。
• 说明:这一步只验证「静态输出」,不跑生命周期、不绑定事件。
- 引入
- 客户端激活测试 (Client Hydration Test)
-
工具:官方
test/e2e
跑 Karma + Mocha + ChromeHeadless ;社区常用 Jest + JSDOM。 -
环境:模拟浏览器(JSDOM 或 Puppeteer)。
-
步骤:
- 把第 2 步生成的 HTML 字符串塞进 JSDOM(
document.body.innerHTML = html
)。 - 在 同一容器 上调
new Vue({ hydrate: true, ... }).$mount('#app')
。 - 断言:
-- DOM diff 后节点未被重复创建(expect(document.querySelectorAll('h1').length).toBe(1)
)。
-- 事件可触发(fireEvent
后检查副作用)。
-- 数据响应式生效(修改vm.msg
,DOM 文本随之更新)。
- 把第 2 步生成的 HTML 字符串塞进 JSDOM(
- Webpack 插件的角色(与测试间接相关)
•server/webpack-plugin/server.js
:在 构建阶段 生成vue-ssr-server-bundle.json
,供createBundleRenderer
读取。
•server/webpack-plugin/client.js
:生成vue-ssr-client-manifest.json
,用于「激活阶段」自动注入<script>
/<link>
。
• 它们本身不参与单元测试;测试时通常直接require
源码或已打包的 CommonJS 产物。
三、简单示例:测试一个组件的 SSR
假设我们有一个简单的 Vue 2 组件。
源码 (src/components/Hello.ssr.vue
)
xml
<template>
<div>
<h1>{{ message }}</h1>
<button @click="count++">You clicked me {{ count }} times.</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, SSR!',
count: 0
}
}
}
</script>
SSR 测试 (tests/ssr/Hello.spec.js
)
我们使用 Jest (配置了 jsdom
环境)来编写这个测试。
javascript
// 1. 引入必要的依赖
import Vue from 'vue';
import { renderToString } from '@vue/server-renderer';
import Hello from '@/components/Hello.ssr.vue';
// 2. 服务端渲染测试
describe('Hello.vue SSR', () => {
it('should render correct contents in SSR', async () => {
// Arrange & Act: 在服务端将组件渲染成字符串
const app = new Vue(Hello);
const html = await renderToString(app);
// Assert: 断言生成的 HTML 字符串包含预期的内容
// 注意:这里测试的是静态字符串,没有事件,没有交互
expect(html).toContain('Hello, SSR!');
expect(html).toContain('You clicked me 0 times.');
// 确保按钮的HTML结构被正确渲染
expect(html).toContain('<button');
});
});
// 3. 客户端激活测试 (这是一个更复杂的例子)
describe('Hello.vue Hydration', () => {
it('should hydrate without mismatches', () => {
// 这部分比较复杂,通常需要以下步骤:
// 1. 获取服务端渲染的 HTML 字符串
const serverHtml = '<div data-server-rendered="true"><h1>Hello, SSR!</h1><button>You clicked me 0 times.</button></div>';
// 2. 在 Jest 的 JSDOM 环境中,创建一个容器并注入服务端HTML
const container = document.createElement('div');
container.innerHTML = serverHtml;
document.body.appendChild(container);
// 3. 在同一个容器上挂载 Vue 实例,进行"激活"
// Vue 会识别 `data-server-rendered="true"` 属性,并进行混合(hydrate)
const app = new Vue({
el: container, // 挂载到已有内容的容器上
components: { Hello },
template: '<hello />'
});
// 4. 断言:激活后DOM结构应保持不变,但组件实例已就绪
expect(container.textContent).toContain('Hello, SSR!');
// 5. 模拟交互,测试响应性是否正常工作
const button = container.querySelector('button');
button.click(); // 在JSDOM中触发点击
// 由于Vue是异步更新的,可能需要使用 Vue.nextTick 或 setTimeout 来断言
expect(app.$children[0].count).toBe(1); // 检查数据是否更新
// 更可靠的断言是检查DOM是否最终更新了
// expect(container.textContent).toContain('You clicked me 1 times.');
});
});
运行测试
和单元测试类似,使用 Jest:
bash
npm test -- tests/ssr/Hello.spec.js
四、Vue 2 源码自身的 SSR 测试
Vue 2 源码本身包含了大量针对其 SSR 能力的测试,这些测试位于 /test/ssr
目录下。它们的目标是确保 Vue 的核心功能在 SSR 模式下表现一致且正确:
- 编译器(Compiler)优化:测试模板编译器在 SSR 模式下是否会生成优化的渲染函数(例如,跳过不必要的响应式标记)。
- 生命周期钩子 :测试组件的
serverPrefetch
、beforeCreate
、created
等生命周期钩子在服务端被正确调用,而mounted
、beforeMount
等浏览器钩子不被调用。 - 指令(Directives) :测试诸如
v-show
,v-model
等指令在只能输出静态 HTML 的服务端如何工作(通常v-show
只渲染样式,v-model
只设置初始 value)。 - 组件缓存:测试服务端组件缓存功能是否正常工作。
- 避免状态污染:测试在单例模式下(Node.js 服务器是长期运行的进程),Vue 应用的状态不会在不同请求间发生污染。这是 SSR 测试的一个重中之重。
Vue 2 源码中 SSR 测试的一个简化概念:
scss
it('should avoid cross-request state pollution', () => {
// 模拟第一个请求
const app1 = createApp();
app1.someState = 'request1'; // 在根实例上设置状态
renderToString(app1);
// 模拟第二个请求
const app2 = createApp();
// 关键断言:第二个应用的状态应该是干净的,没有被第一个请求污染
expect(app2.someState).toBeUndefined();
});
总结
特性 | SSR 测试 |
---|---|
测试什么 | Vue 应用在 Node.js 服务器 上的渲染结果,以及客户端激活过程 |
核心挑战 | 环境差异 (Node.js vs. Browser)、避免污染 、同构一致性 |
关键阶段 | 1. 服务端字符串渲染 2. 客户端混合(Hydration) |
主要断言 | 1. HTML 输出是否正确(字符串匹配) 2. Hydration 后 DOM 是否一致、交互是否正常 |
比喻 | 测试"中央厨房"能否做出正确的成品蛋糕,以及附上的食谱能否让顾客顺利加工。 |
结论 :
Vue 2 的 SSR 测试是保证其"同构"能力稳定性的基石。它通过在 Node.js 环境中运行测试来验证静态渲染 ,再在模拟的浏览器环境中验证动态激活,确保开发者能够构建出既拥有良好首屏性能(SSR 优势)又具备丰富交互(SPA 优势)的应用程序。对于 Vue 2 源码本身,这些测试至关重要,它们覆盖了从编译器到运行时、从生命周期到状态管理的每一个可能与 SSR 相关的细节。