Ember集成测试 #

一、集成测试概述 #

集成测试用于测试组件的渲染和交互行为,验证组件是否正确工作。

1.1 生成测试 #

bash
ember generate component-test user-card
ember generate integration-test user-card

1.2 测试结构 #

javascript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | user-card', function (hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function (assert) {
    await render(hbs`<UserCard />`);

    assert.dom(this.element).hasText('');
  });
});

二、渲染测试 #

2.1 基本渲染 #

javascript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | user-card', function (hooks) {
  setupRenderingTest(hooks);

  test('it renders user information', async function (assert) {
    this.set('user', {
      name: 'John Doe',
      email: 'john@example.com',
    });

    await render(hbs`<UserCard @user={{this.user}} />`);

    assert.dom('.user-name').hasText('John Doe');
    assert.dom('.user-email').hasText('john@example.com');
  });
});

2.2 条件渲染 #

javascript
test('it shows email when showEmail is true', async function (assert) {
  this.set('user', { name: 'John', email: 'john@example.com' });
  this.set('showEmail', true);

  await render(hbs`<UserCard @user={{this.user}} @showEmail={{this.showEmail}} />`);

  assert.dom('.user-email').exists();

  this.set('showEmail', false);

  assert.dom('.user-email').doesNotExist();
});

2.3 列表渲染 #

javascript
test('it renders list of items', async function (assert) {
  this.set('items', [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
  ]);

  await render(hbs`<ItemList @items={{this.items}} />`);

  assert.dom('.item').exists({ count: 3 });
  assert.dom('.item:first-child').hasText('Item 1');
});

三、交互测试 #

3.1 点击测试 #

javascript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | counter', function (hooks) {
  setupRenderingTest(hooks);

  test('it increments on click', async function (assert) {
    await render(hbs`<Counter />`);

    assert.dom('.count').hasText('0');

    await click('.increment');

    assert.dom('.count').hasText('1');

    await click('.increment');

    assert.dom('.count').hasText('2');
  });
});

3.2 输入测试 #

javascript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | search-input', function (hooks) {
  setupRenderingTest(hooks);

  test('it updates value on input', async function (assert) {
    await render(hbs`<SearchInput />`);

    await fillIn('input', 'search term');

    assert.dom('input').hasValue('search term');
  });

  test('it calls onSearch on Enter', async function (assert) {
    assert.expect(1);

    this.set('handleSearch', (term) => {
      assert.strictEqual(term, 'search term');
    });

    await render(hbs`<SearchInput @onSearch={{this.handleSearch}} />`);

    await fillIn('input', 'search term');
    await triggerKeyEvent('input', 'keyup', 'Enter');
  });
});

3.3 表单测试 #

javascript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | login-form', function (hooks) {
  setupRenderingTest(hooks);

  test('it submits form with credentials', async function (assert) {
    assert.expect(2);

    this.set('handleLogin', (credentials) => {
      assert.deepEqual(credentials, {
        email: 'test@example.com',
        password: 'password123',
      });
    });

    await render(hbs`<LoginForm @onSubmit={{this.handleLogin}} />`);

    await fillIn('[data-test-email]', 'test@example.com');
    await fillIn('[data-test-password]', 'password123');
    await click('[data-test-submit]');
  });
});

四、异步测试 #

4.1 等待渲染 #

javascript
import { settled } from '@ember/test-helpers';

test('it handles async state', async function (assert) {
  this.set('isLoading', true);

  await render(hbs`<LoadingSpinner @isLoading={{this.isLoading}} />`);

  assert.dom('.spinner').exists();

  this.set('isLoading', false);
  await settled();

  assert.dom('.spinner').doesNotExist();
});

4.2 等待动画 #

javascript
import { waitFor } from '@ember/test-helpers';

test('it shows modal after animation', async function (assert) {
  await render(hbs`<Modal @isOpen={{true}} />`);

  await waitFor('.modal-content');

  assert.dom('.modal-content').isVisible();
});

五、测试助手 #

5.1 自定义测试助手 #

javascript
// tests/helpers/login.js
import { fillIn, click } from '@ember/test-helpers';

export async function login(email, password) {
  await fillIn('[data-test-email]', email);
  await fillIn('[data-test-password]', password);
  await click('[data-test-submit]');
}
javascript
// tests/acceptance/dashboard-test.js
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, currentURL } from '@ember/test-helpers';
import { login } from '../helpers/login';

module('Acceptance | dashboard', function (hooks) {
  setupApplicationTest(hooks);

  test('visiting dashboard after login', async function (assert) {
    await visit('/login');
    await login('test@example.com', 'password');

    assert.strictEqual(currentURL(), '/dashboard');
  });
});

六、DOM断言 #

6.1 qunit-dom断言 #

javascript
// 存在性
assert.dom('.element').exists();
assert.dom('.element').exists({ count: 3 });

// 文本内容
assert.dom('.element').hasText('Hello');
assert.dom('.element').hasText(/Hello/);
assert.dom('.element').includesText('Hello');

// 属性
assert.dom('input').hasValue('test');
assert.dom('a').hasAttribute('href', '/path');
assert.dom('button').hasClass('active');
assert.dom('button').isDisabled();

// 可见性
assert.dom('.element').isVisible();
assert.dom('.element').isNotVisible();

// 焦点
assert.dom('input').isFocused();

七、最佳实践 #

7.1 使用data-test属性 #

handlebars
{{! 组件}}
<button data-test-submit type="submit">提交</button>
<input data-test-email type="email" />
javascript
// 测试
await click('[data-test-submit]');
await fillIn('[data-test-email]', 'test@example.com');

7.2 测试用户行为 #

javascript
// 好的做法 - 测试用户行为
test('user can submit form', async function (assert) {
  await fillIn('[data-test-email]', 'test@example.com');
  await click('[data-test-submit]');
  assert.dom('.success-message').exists();
});

// 避免 - 测试实现细节
test('form has correct state', async function (assert) {
  assert.strictEqual(component.isValid, true);
});

八、总结 #

集成测试要点:

方法 用途
render 渲染组件
click 点击元素
fillIn 填写输入
triggerKeyEvent 触发键盘事件
settled 等待异步完成

集成测试是验证组件行为的重要手段。

最后更新:2026-03-28