Ember应用测试 #
一、应用测试概述 #
应用测试(Acceptance Test)用于测试完整的用户流程,模拟真实用户行为。
1.1 生成测试 #
bash
ember generate acceptance-test login
ember generate acceptance-test user-flow
1.2 测试结构 #
javascript
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, currentURL } from '@ember/test-helpers';
module('Acceptance | login', function (hooks) {
setupApplicationTest(hooks);
test('visiting /login', async function (assert) {
await visit('/login');
assert.strictEqual(currentURL(), '/login');
});
});
二、导航测试 #
2.1 页面访问 #
javascript
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, currentURL } from '@ember/test-helpers';
module('Acceptance | navigation', function (hooks) {
setupApplicationTest(hooks);
test('visiting home page', async function (assert) {
await visit('/');
assert.strictEqual(currentURL(), '/');
assert.dom('h1').hasText('Welcome');
});
test('navigating to about page', async function (assert) {
await visit('/');
await click('a[href="/about"]');
assert.strictEqual(currentURL(), '/about');
});
});
2.2 路由保护 #
javascript
module('Acceptance | protected routes', function (hooks) {
setupApplicationTest(hooks);
test('redirects to login when not authenticated', async function (assert) {
await visit('/admin');
assert.strictEqual(currentURL(), '/login');
});
test('can access admin when authenticated', async function (assert) {
// 模拟登录
const session = this.owner.lookup('service:session');
session.login({ id: 1, role: 'admin' });
await visit('/admin');
assert.strictEqual(currentURL(), '/admin');
});
});
三、用户流程测试 #
3.1 登录流程 #
javascript
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, fillIn, click, currentURL } from '@ember/test-helpers';
module('Acceptance | login', function (hooks) {
setupApplicationTest(hooks);
test('user can login', async function (assert) {
await visit('/login');
await fillIn('[data-test-email]', 'user@example.com');
await fillIn('[data-test-password]', 'password');
await click('[data-test-submit]');
assert.strictEqual(currentURL(), '/dashboard');
assert.dom('[data-test-user-name]').hasText('User');
});
test('shows error on invalid credentials', async function (assert) {
await visit('/login');
await fillIn('[data-test-email]', 'wrong@example.com');
await fillIn('[data-test-password]', 'wrong');
await click('[data-test-submit]');
assert.dom('[data-test-error]').hasText('Invalid credentials');
});
});
3.2 CRUD流程 #
javascript
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, fillIn, click, currentURL } from '@ember/test-helpers';
module('Acceptance | posts', function (hooks) {
setupApplicationTest(hooks);
test('user can create a post', async function (assert) {
await visit('/posts/new');
await fillIn('[data-test-title]', 'New Post');
await fillIn('[data-test-body]', 'Post content');
await click('[data-test-submit]');
assert.strictEqual(currentURL(), '/posts/1');
assert.dom('h1').hasText('New Post');
});
test('user can edit a post', async function (assert) {
// 创建测试数据
const store = this.owner.lookup('service:store');
store.createRecord('post', { id: 1, title: 'Old Title', body: 'Body' });
await visit('/posts/1/edit');
await fillIn('[data-test-title]', 'Updated Title');
await click('[data-test-submit]');
assert.dom('h1').hasText('Updated Title');
});
test('user can delete a post', async function (assert) {
const store = this.owner.lookup('service:store');
store.createRecord('post', { id: 1, title: 'Post' });
await visit('/posts/1');
await click('[data-test-delete]');
assert.strictEqual(currentURL(), '/posts');
assert.dom('[data-test-post]').doesNotExist();
});
});
四、模拟API #
4.1 使用Pretender #
bash
ember install ember-pretenderify
javascript
// tests/acceptance/posts-test.js
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit } from '@ember/test-helpers';
import Pretender from 'pretender';
module('Acceptance | posts', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.server = new Pretender();
this.server.get('/api/posts', () => {
return [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
posts: [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' },
],
}),
];
});
});
hooks.afterEach(function () {
this.server.shutdown();
});
test('list posts', async function (assert) {
await visit('/posts');
assert.dom('[data-test-post]').exists({ count: 2 });
});
});
4.2 使用Mirage #
bash
ember install ember-cli-mirage
javascript
// tests/acceptance/posts-test.js
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Acceptance | posts', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
test('list posts', async function (assert) {
this.server.createList('post', 3);
await visit('/posts');
assert.dom('[data-test-post]').exists({ count: 3 });
});
});
五、测试助手 #
5.1 常用测试助手 #
javascript
import {
visit, // 访问URL
currentURL, // 获取当前URL
click, // 点击
fillIn, // 填写输入
triggerKeyEvent, // 触发键盘事件
waitFor, // 等待元素
settled, // 等待异步完成
pauseTest, // 暂停测试
resumeTest, // 恢复测试
} from '@ember/test-helpers';
5.2 自定义测试助手 #
javascript
// tests/helpers/authentication.js
import { fillIn, click } from '@ember/test-helpers';
export async function authenticate(email, password) {
await fillIn('[data-test-email]', email);
await fillIn('[data-test-password]', password);
await click('[data-test-submit]');
}
export async function logout() {
await click('[data-test-logout]');
}
javascript
// tests/acceptance/dashboard-test.js
import { authenticate } from '../helpers/authentication';
test('authenticated user can see dashboard', async function (assert) {
await visit('/login');
await authenticate('user@example.com', 'password');
assert.strictEqual(currentURL(), '/dashboard');
});
六、调试测试 #
6.1 暂停测试 #
javascript
test('debug test', async function (assert) {
await visit('/');
await pauseTest(); // 暂停测试
// 在浏览器控制台输入 resumeTest() 继续
});
6.2 查看DOM #
javascript
test('inspect DOM', async function (assert) {
await visit('/');
console.log(this.element.innerHTML);
});
七、最佳实践 #
7.1 测试用户视角 #
javascript
// 好的做法 - 从用户视角测试
test('user can search', async function (assert) {
await visit('/search');
await fillIn('[data-test-search-input]', 'ember');
await click('[data-test-search-button]');
assert.dom('[data-test-result]').exists();
});
// 避免 - 测试内部状态
test('search state', async function (assert) {
const controller = this.owner.lookup('controller:search');
assert.strictEqual(controller.searchTerm, 'ember');
});
7.2 合理的测试覆盖 #
javascript
// 测试主要用户流程
test('complete checkout flow', async function (assert) {
// 添加商品到购物车
await visit('/products/1');
await click('[data-test-add-to-cart]');
// 查看购物车
await visit('/cart');
assert.dom('[data-test-cart-item]').exists();
// 结账
await click('[data-test-checkout]');
await fillIn('[data-test-card-number]', '4242424242424242');
await click('[data-test-place-order]');
assert.dom('[data-test-order-confirmation]').exists();
});
八、总结 #
应用测试要点:
| 方法 | 用途 |
|---|---|
| visit | 访问页面 |
| click | 点击元素 |
| fillIn | 填写表单 |
| currentURL | 获取当前URL |
| pauseTest | 调试暂停 |
应用测试验证完整的用户流程,是质量保障的重要环节。
最后更新:2026-03-28