MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Svelte 组件测试:使用 Jest 和 Testing Library

2024-10-252.7k 阅读

安装 Jest 和 Testing Library

在开始使用 Jest 和 Testing Library 进行 Svelte 组件测试之前,我们需要先在项目中安装它们。假设你已经有一个 Svelte 项目,并且项目使用 npmyarn 作为包管理器。

首先,安装 Jest。在项目根目录下运行以下命令:

npm install --save-dev jest

或者使用 yarn

yarn add --dev jest

接下来,安装 Svelte 相关的 Jest 配置。这能让 Jest 正确处理 Svelte 组件。运行:

npm install --save-dev @sveltejs/jest

yarn add --dev @sveltejs/jest

然后,安装 Testing Library 及其 Svelte 专用版本。Testing Library 是一组用于测试 React、Vue、Svelte 等各种前端框架组件的工具,它强调从用户视角进行测试。

npm install --save-dev @testing-library/jest-dom @testing-library/svelte

yarn add --dev @testing-library/jest-dom @testing-library/svelte

安装完成后,我们还需要对 Jest 进行一些配置。在项目根目录下创建或编辑 jest.config.js 文件,添加如下内容:

module.exports = {
  preset: '@sveltejs/jest',
  moduleFileExtensions: ['js', 'json','svelte'],
  transform: {
    '^.+\\.svelte$': '@sveltejs/jest',
    '^.+\\.js$': 'babel-jest'
  },
  setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect']
};

上述配置中,preset 指定了使用 @sveltejs/jest 预设,moduleFileExtensions 告诉 Jest 哪些文件扩展名需要处理,transform 配置了如何转换 Svelte 和 JavaScript 文件,setupFilesAfterEnv 引入了 @testing-library/jest-dom 的扩展断言,这能让我们在测试中使用更友好的断言方式。

编写第一个 Svelte 组件测试

假设我们有一个简单的 Svelte 组件 Button.svelte,代码如下:

<script>
  let text = 'Click me';
  let count = 0;

  const handleClick = () => {
    count++;
  };
</script>

<button on:click={handleClick}>
  {text} {count}
</button>

这个组件是一个简单的按钮,显示“Click me”字样以及点击次数。现在我们来编写测试用例,确保按钮的功能正常。

Button.svelte 同一目录下创建 Button.test.js 文件(测试文件命名一般遵循 组件名.test.js 的规则)。

import { render, screen } from '@testing-library/svelte';
import Button from './Button.svelte';

describe('Button Component', () => {
  test('renders button with initial text', () => {
    render(Button);
    const button = screen.getByText('Click me 0');
    expect(button).toBeInTheDocument();
  });

  test('increments count on click', () => {
    render(Button);
    const button = screen.getByText('Click me 0');
    button.click();
    const updatedButton = screen.getByText('Click me 1');
    expect(updatedButton).toBeInTheDocument();
  });
});

在上述测试代码中:

  1. 我们从 @testing-library/svelte 导入了 renderscreenrender 用于渲染 Svelte 组件,screen 提供了一系列查询函数来查找渲染后的组件元素。
  2. 使用 describe 块来分组测试用例,这里我们将测试用例归为“Button Component”组。
  3. 第一个 test 用例检查按钮是否以初始文本正确渲染。通过 render 渲染 Button 组件后,使用 screen.getByText 查找包含“Click me 0”文本的元素,并使用 Jest 的 expect 断言该元素存在于文档中。
  4. 第二个 test 用例验证按钮点击后计数是否增加。同样先渲染组件,找到初始按钮并模拟点击,然后再次查找更新后的文本“Click me 1”,断言更新后的按钮存在于文档中。

测试组件的属性传递

Svelte 组件常常接收属性(props)来定制其行为和外观。假设我们有一个 Greeting.svelte 组件,它接收一个 name 属性来显示个性化问候:

<script>
  export let name = 'World';
</script>

<p>Hello, {name}!</p>

我们来编写测试用例,验证属性传递是否正确。在 Greeting.svelte 同一目录下创建 Greeting.test.js 文件:

import { render, screen } from '@testing-library/svelte';
import Greeting from './Greeting.svelte';

describe('Greeting Component', () => {
  test('renders default greeting', () => {
    render(Greeting);
    const greeting = screen.getByText('Hello, World!');
    expect(greeting).toBeInTheDocument();
  });

  test('renders custom greeting', () => {
    render(Greeting, { name: 'John' });
    const greeting = screen.getByText('Hello, John!');
    expect(greeting).toBeInTheDocument();
  });
});

在这个测试代码中:

  1. 第一个 test 用例检查组件在未传递 name 属性时,是否显示默认问候“Hello, World!”。
  2. 第二个 test 用例通过 render 函数的第二个参数传递 name: 'John' 属性,验证组件是否显示定制的问候“Hello, John!”。

测试组件的事件处理

有时候我们需要测试组件如何处理外部触发的事件。例如,我们有一个 InputWithSubmit.svelte 组件,它包含一个输入框和一个提交按钮,提交时会触发一个自定义事件并传递输入的值:

<script>
  let inputValue = '';
  const handleSubmit = () => {
    const customEvent = new CustomEvent('submit', { detail: inputValue });
    $dispatch(customEvent);
  };
</script>

<input bind:value={inputValue} />
<button on:click={handleSubmit}>Submit</button>

现在编写测试用例来验证事件是否正确触发以及传递的值是否正确。在 InputWithSubmit.svelte 同一目录下创建 InputWithSubmit.test.js 文件:

import { render } from '@testing-library/svelte';
import InputWithSubmit from './InputWithSubmit.svelte';

describe('InputWithSubmit Component', () => {
  test('triggers submit event with correct value', () => {
    const { component } = render(InputWithSubmit);
    const input = component.shadowRoot.querySelector('input');
    const button = component.shadowRoot.querySelector('button');

    input.value = 'Test Value';
    input.dispatchEvent(new Event('input', { bubbles: true }));
    button.click();

    let submitValue;
    component.$on('submit', (event) => {
      submitValue = event.detail;
    });

    expect(submitValue).toBe('Test Value');
  });
});

在上述测试代码中:

  1. 通过 render 获取组件实例 component
  2. 使用 component.shadowRoot.querySelector 找到输入框和按钮元素。
  3. 模拟输入框输入值并触发 input 事件,然后模拟点击按钮触发提交。
  4. 通过 component.$on 监听 submit 事件,并在事件回调中获取传递的值 submitValue
  5. 最后使用 expect 断言传递的值是否为“Test Value”。

测试组件的状态变化

许多 Svelte 组件依赖内部状态来呈现不同的 UI。例如,我们有一个 Toggle.svelte 组件,它有一个布尔状态 isOn,通过点击按钮切换该状态并显示相应文本:

<script>
  let isOn = false;
  const toggle = () => {
    isOn =!isOn;
  };
</script>

<button on:click={toggle}>
  {isOn? 'Turn Off' : 'Turn On'}
</button>

下面编写测试用例来验证状态变化和 UI 更新。在 Toggle.svelte 同一目录下创建 Toggle.test.js 文件:

import { render, screen } from '@testing-library/svelte';
import Toggle from './Toggle.svelte';

describe('Toggle Component', () => {
  test('renders with initial state', () => {
    render(Toggle);
    const button = screen.getByText('Turn On');
    expect(button).toBeInTheDocument();
  });

  test('toggles state on click', () => {
    render(Toggle);
    const button = screen.getByText('Turn On');
    button.click();
    const updatedButton = screen.getByText('Turn Off');
    expect(updatedButton).toBeInTheDocument();
  });
});

在这个测试代码中:

  1. 第一个 test 用例验证组件初始渲染时按钮文本为“Turn On”。
  2. 第二个 test 用例模拟按钮点击,验证状态切换后按钮文本更新为“Turn Off”。

测试组件的样式

虽然 Jest 和 Testing Library 主要关注功能测试,但我们也可以通过一些方式测试组件的样式。例如,我们有一个 StyledButton.svelte 组件,它有特定的背景颜色和文本颜色:

<script>
  let text = 'Styled Button';
</script>

<style>
  button {
    background-color: blue;
    color: white;
  }
</style>

<button>{text}</button>

我们可以测试按钮是否应用了正确的样式。在 StyledButton.svelte 同一目录下创建 StyledButton.test.js 文件:

import { render } from '@testing-library/svelte';
import StyledButton from './StyledButton.svelte';

describe('StyledButton Component', () => {
  test('has correct background color', () => {
    const { container } = render(StyledButton);
    const button = container.firstChild;
    const backgroundColor = window.getComputedStyle(button).getPropertyValue('background-color');
    expect(backgroundColor).toBe('rgb(0, 0, 255)');
  });

  test('has correct text color', () => {
    const { container } = render(StyledButton);
    const button = container.firstChild;
    const textColor = window.getComputedStyle(button).getPropertyValue('color');
    expect(textColor).toBe('rgb(255, 255, 255)');
  });
});

在上述测试代码中:

  1. 通过 render 获取包含组件的容器 container
  2. 使用 window.getComputedStyle 获取按钮的计算样式,并通过 getPropertyValue 获取背景颜色和文本颜色。
  3. 使用 expect 断言颜色值是否符合预期。

测试组件的生命周期

Svelte 组件有自己的生命周期函数,如 onMountbeforeUpdateafterUpdate 等。我们可以测试这些生命周期函数是否正确执行。假设我们有一个 LifecycleComponent.svelte 组件:

<script>
  let mounted = false;
  let updated = false;

  onMount(() => {
    mounted = true;
  });

  beforeUpdate(() => {
    updated = false;
  });

  afterUpdate(() => {
    updated = true;
  });
</script>

{#if mounted}
  <p>Component is mounted. Update status: {updated? 'Updated' : 'Not Updated'}</p>
{/if}

现在编写测试用例来验证生命周期函数。在 LifecycleComponent.svelte 同一目录下创建 LifecycleComponent.test.js 文件:

import { render, screen } from '@testing-library/svelte';
import LifecycleComponent from './LifecycleComponent.svelte';

describe('LifecycleComponent Component', () => {
  test('mounts component and updates status', () => {
    render(LifecycleComponent);
    const mountedText = screen.getByText('Component is mounted. Update status: Not Updated');
    expect(mountedText).toBeInTheDocument();
  });

  test('updates component and changes status', async () => {
    const { component } = render(LifecycleComponent);
    component.$set({});
    await component.$$.flush();
    const updatedText = screen.getByText('Component is mounted. Update status: Updated');
    expect(updatedText).toBeInTheDocument();
  });
});

在这个测试代码中:

  1. 第一个 test 用例验证组件挂载后,显示的文本表明组件已挂载且未更新。
  2. 第二个 test 用例通过 component.$set({}) 触发组件更新,await component.$$.flush() 等待更新完成,然后验证显示的文本表明组件已更新。

测试异步操作

在 Svelte 组件中,我们常常会遇到异步操作,如数据获取。假设我们有一个 AsyncData.svelte 组件,它在组件挂载时异步获取数据并显示:

<script>
  let data = null;
  onMount(async () => {
    const response = await fetch('https://example.com/api/data');
    const result = await response.json();
    data = result;
  });
</script>

{#if data}
  <p>{data.message}</p>
{:else}
  <p>Loading...</p>
{/if}

我们来编写测试用例,模拟异步数据获取并验证组件显示。在 AsyncData.svelte 同一目录下创建 AsyncData.test.js 文件:

import { render, screen, waitFor } from '@testing-library/svelte';
import AsyncData from './AsyncData.svelte';
import fetchMock from 'fetch-mock';

describe('AsyncData Component', () => {
  afterEach(() => {
    fetchMock.restore();
  });

  test('shows loading text initially', () => {
    render(AsyncData);
    const loadingText = screen.getByText('Loading...');
    expect(loadingText).toBeInTheDocument();
  });

  test('shows data after fetching', async () => {
    const mockData = { message: 'Mocked Data' };
    fetchMock.getOnce('https://example.com/api/data', mockData);
    render(AsyncData);
    await waitFor(() => screen.getByText('Mocked Data'));
  });
});

在上述测试代码中:

  1. 使用 fetchMock 库来模拟 fetch 操作。在每个测试用例后通过 fetchMock.restore() 恢复原始的 fetch 行为。
  2. 第一个 test 用例验证组件初始显示“Loading...”文本。
  3. 第二个 test 用例使用 fetchMock.getOnce 模拟数据获取,并通过 waitFor 等待组件显示模拟的数据“Mocked Data”。

测试嵌套组件

Svelte 组件常常包含嵌套组件。假设我们有一个 ParentComponent.svelte 组件,它包含一个 ChildComponent.svelte 组件:

ChildComponent.svelte

<script>
  export let text = 'Default Text';
</script>

<p>{text}</p>

ParentComponent.svelte

<script>
  import ChildComponent from './ChildComponent.svelte';
</script>

<ChildComponent text="Nested Text" />

现在编写测试用例来验证嵌套组件是否正确渲染。在 ParentComponent.svelte 同一目录下创建 ParentComponent.test.js 文件:

import { render, screen } from '@testing-library/svelte';
import ParentComponent from './ParentComponent.svelte';

describe('ParentComponent Component', () => {
  test('renders nested child component with correct text', () => {
    render(ParentComponent);
    const childText = screen.getByText('Nested Text');
    expect(childText).toBeInTheDocument();
  });
});

在这个测试代码中,通过渲染 ParentComponent 并查找 ChildComponent 传递的文本“Nested Text”,验证嵌套组件是否正确渲染。

处理测试中的错误

在测试过程中,可能会遇到各种错误,如组件渲染失败、断言失败等。例如,假设我们有一个 ErrorComponent.svelte 组件,在特定条件下会抛出错误:

<script>
  export let shouldError = false;
  if (shouldError) {
    throw new Error('Test Error');
  }
</script>

<p>Component without error</p>

我们编写测试用例来验证错误是否正确抛出。在 ErrorComponent.svelte 同一目录下创建 ErrorComponent.test.js 文件:

import { render } from '@testing-library/svelte';
import ErrorComponent from './ErrorComponent.svelte';

describe('ErrorComponent Component', () => {
  test('renders without error when shouldError is false', () => {
    render(ErrorComponent);
  });

  test('throws error when shouldError is true', () => {
    expect(() => render(ErrorComponent, { shouldError: true })).toThrow('Test Error');
  });
});

在上述测试代码中:

  1. 第一个 test 用例验证当 shouldErrorfalse 时,组件正常渲染。
  2. 第二个 test 用例使用 expect(() =>...).toThrow 断言当 shouldErrortrue 时,渲染组件会抛出“Test Error”错误。

通过以上详细的步骤和示例,你应该对使用 Jest 和 Testing Library 进行 Svelte 组件测试有了全面的了解。从基础的安装配置到各种复杂场景的测试用例编写,这些知识将帮助你确保 Svelte 组件的质量和可靠性。在实际项目中,根据组件的具体功能和需求,灵活运用这些测试技巧,可以大大提高开发效率和代码的稳定性。