顯示具有 Jest 標籤的文章。 顯示所有文章
顯示具有 Jest 標籤的文章。 顯示所有文章

2023年1月27日 星期五

[Jest+Testing Library] Vue + storybook interaction 元件測試範例


storybook 可以獨立建立元件,並搭配 @storybook/addon-controls 控制 props 輸入及 @storybook/addon-actions 顯示 emit 輸出的內容,當然可以透過手動開啟 GUI 來測試元件是否符合規格,storybook 已經完成 render 的步驟,自動化的測試只差一步了!

storybook 可以搭配 interaction 套件進行元件測試,先按照「[Vue.js] storybook安裝方式與環境建置」完成 vue+storybook 的安裝,文內還是使用 Vue2,如果使用 Vue3 其實步驟差異不大。


安裝

執行指令

npm install --save-dev @storybook/addon-interactions @storybook/jest @storybook/testing-library


設定

請開啟 .storybook/main.js,註冊這個 addon interactions 套件,@storybook/addon-interactions 一定要放在 @storybook/addon-actions@storybook/addon-essentials 的後面。

module.exports = {
    addons: [
        '@storybook/addon-actions', // or '@storybook/addon-essentials'
        '@storybook/addon-interactions',
    ],
    features: {
        interactionsDebugger: true,
    },
};


撰寫測試

寫測試前,要先了解測試元件的重點是「邏輯!」

第一視覺的邏輯,經由 props 或 slots 改變元件顯示上的差異,例如文字內容或是決定是否顯示哪些區域等;第二行為的邏輯,當使用者輸入或點擊按鈕等,此時畫面產生的變化或是 emit 的事件內容等。

使用 jest + testing library 做測試,testing library底層為 vue test utils,兩者相比 testing library 可以更聚焦於元件測試,不容易因為非邏輯的變更,連同測試都要一起做調整。


簡單寫一個元件 NameEditor.vue,有兩個 props,按下按鈕可以發出 event。

<template>
    <div>
        <div
            v-text="message" />
        <input
            type="text"
            placeholder="name"
            v-model.trim="name">
        <button
            type="button"
            @click="onButtonClick"
            v-text="buttonText" />
    </div>
</template>

<script>
import {ref} from 'vue';

export default {
    props: {
        message: {
            type: String,
            required: true,
        },
        buttonText: {
            type: String,
            required: true,
        },
    },
    setup(props, {emit}) {
        const name = ref(undefined);

        const onButtonClick = () => {
            emit('subit', name);
        };

        return {
            name,
            onButtonClick,
        };
    },
};
</script>

接著,NameEditor.stories.js story基本的內容:

import {userEvent, screen} from '@storybook/testing-library';
import {expect} from '@storybook/jest';
import NameEditor from './NameEditor';

export default {
    title: 'NameEditor',
    argTypes: {
        message: {
            control: 'text',
        },
        buttonText: {
            control: 'text',
        },
        onNameSubmit: {
            action: 'submit',
        },
    },
};

const Template = (args, {argTypes}) => ({
    props: Object.keys(argTypes),
    components: {
        NameEditor,
    },
    template: `
        <NameEditor
            :message="message"
            :button-text="buttonText"
            @submit="onNameSubmit" />
    `,
});

export const Default = Template.bind({});

Default.args = {
    message: 'Input your name',
    buttonText: 'Submit',
};

interaction 的測試,必須寫在 play 裡面,測試流程是先輸入文字後,按下按鈕:

Default.play = async ({args}) => {
    const mockName = 'Chenuin';
    const button = screen.getByRole('button');

    await userEvent.type(screen.getByPlaceholderText('name'), mockName); // input name
    await userEvent.click(button); // click button

    const {message, buttonText, onNameSubmit} = args;

    expect(onNameSubmit).toHaveBeenCalledTimes(1);
    expect(onNameSubmit).toBeCalledWith(mockName);

    expect(screen.getByText(message)).toBeTruthy();
    expect(button.textContent).toBe(buttonText);
};

可以透過 args 取得 argTypes 的內容,發出的 event 自動 mock 可以直接驗證,只要把握 testing library獲取Dom的方式,需要注意 getBy...queryBy... 兩者差別,前者若取不到任何相符條件的element,會拋出exception,後者不會。其他部分就是 jest 語法。



執行

開啟 storybook 就會自動執行測試內容,如果有錯誤也會將訊息顯示畫面。

npm run storybook

(畫面如下)


到這邊就告一段落,如果希望能夠導入CI,可以使用 @storybook/test-runner

npm install --save-dev @storybook/test-runner

開啟 package.json 加入 npm script

{
  "scripts": {
    "test-storybook": "npx test-storybook"
  }
}

預設 test runner 會找到 http://localhost:6006 進行測試,可以加上 --url 來改變路徑。

npm run test-storybook

如果不是本地啟用storybook,可以利用 https://www.chromatic.com/ 來管理 storybook,就可以透過 internet 進行。Chromatic 可以用來發布 storybook、主要方便團隊 UI Review,藉此進行 CI 測試整合會更加容易。



參考資料

storybook 套件


2023年1月14日 星期六

第一個 Vue3 元件測試從 Vitest + Vue Test Utils 開始

延續「mocha + webpack 的 Vue3 元件單元測試」,既然使用 mocha 測試太卡關,這次安裝 Vitest 來試試看!

一、安裝

先安裝 Vitest,另外 Vitest 需要使用 Node >=v14,並安裝 Vite >=v3.0.0,還有 vue3 對應的測試套件

npm install --save-dev vite vitest @vue/test-utils @vitejs/plugin-vue

下面這幾個根據需求來選擇是否安裝

npm install --save-dev jsdom

提供UI介面,可參考 Test UI

npm install --save-dev @vitest/ui


二、設定

先到package.json設定npm指令

"scripts": {
    "test": "npx vitest",
}

在根目錄新增檔案 vitest.config.js

import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [
        Vue(),
    ],
    test: {
        globals: true,
        environment: 'jsdom',
    },
});

常用的設定像是include指定測試檔案的目錄或名稱等,和exclude排除檔案

export default defineConfig({
    test: {
        include: ['**/*.test.js'],
        exclude: ['**/node_modules/**'],
    },
});

設定路徑的alias

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src/'),
        },
    },
});

或者可以設定 csssetupFiles來套用css style

export default defineConfig({
    test: {
        css: true,
        setupFiles: './src/vitest/setup.js',
    },
});

src/vitest/setup.js import css

// src/vitest/setup.js

import 'todomvc-app-css/index.css';
import 'todomvc-common/base.css';

其他設定可以參考官方文件

完成基本設定後,就可以開始寫測試囉!



三、測試

針對現有的元件 TodoListEditor 撰寫測試

import { mount } from '@vue/test-utils';
import { test, expect } from 'vitest';
import TodoListEditor from 'components/TodoListEditor.vue';

test('TodoListEditor', () => {
    expect(TodoListEditor).toBeTruthy()

    const wrapper = mount(TodoListEditor, {
        props: {
            todoList,
        },
    });

    expect(wrapper.vm.todoList).toStrictEqual(todoList);
    expect(wrapper.props('todoList')).toStrictEqual(todoList);
});

參考資料



四、執行
npm run test

執行後就可以看到所有的測試項目及結果,若有錯誤也會對應的訊息顯示

有安裝@vitest/ui,可以開啟ui試試看

npm run test -- --ui


五、CI/CD

有了測試之後,我們利用 github action 設定每次 push 分支時,執行 vitest 的測試,請新增檔案 .github/workflows/test.yml

name: Test

on:
  push:
    branches: main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Before srcipt
        run: npm install

      - name: Test
        run: npm run test -- --run

.github/workflows/gh-pages.yml內的設定:

on:
  workflow_run:
    workflows: ["Test"]
    types:
      - completed

jobs:
  build:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest

    steps:
      ....

我們的目標是當 Test 成功時,才會執行部屬,否則忽略。請注意 completed 指的是完成時,無論測試結果成功與否都會觸發。因此 if: ${{ github.event.workflow_run.conclusion == 'success' }} 的設定是必要的。

也可變化成 ${{ github.event.workflow_run.conclusion == 'failure' }},當測試結果失敗時可以發送通知,根據應用情境改寫。

workflows可以設定多項,只要有任何一項執行就會觸發。


安裝 Vitest 來進行 Vue3 測試十分容易。語法跟 jest 很相像,甚至原本的 mocha 寫的測試也一行都沒改,都能夠支援測試。以上的內容有上傳到 github 上(專案),可以點選下方連結了解執行的內容。


2022年5月28日 星期六

Vue3+jest 測試 composable 範例


Vue3 的 composable 乍看下和 mixin 用途很類似,可以提供各個元件共用程式碼。但與 mixin 相比,composable 主要有三個優勢:

第一,元件可以很明確的區分使用的 composable 來源,當使用的 mixin 一多時,追朔來源相對困難,無法一眼看出由哪個 mixin 實作。

第二,多個 mixin 無法確保使用了相同的名稱,可能造成覆蓋,但 composable 即使有相同的名稱,也能透過結構式賦值、重新命名。

最後,多個 mixin 需要交互作用時,通常會使用相同的參數命名來達到這個目的,隱性的耦合使得辨識和debug難度增加,composable可藉由其一的回傳值,作為其他composable輸入的參數達到共享的目的。(參考資料)


安裝 Jest 測試 vue 時,首先要注意版本,請參考下面的對照表安裝套件:

Vue version Jest Version npm Package

Vue 2 Jest 26 and below vue-jest@4

Vue 3 Jest 26 and below vue-jest@5

Vue 2 Jest 27 and above @vue/vue2-jest@27

Vue 3 Jest 27 and above @vue/vue3-jest@27

Vue 2 Jest 28 and above @vue/vue2-jest@28

Vue 3 Jest 28 and above @vue/vue3-jest@28


composable 的測試主要分成兩種,第一種元件比較無關,可以當作普通的 js code 測試。

// counter.js
import {ref} from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;

  return {
    count,
    increment,
  };
};
// counter.test.js
import {useCounter} from './counter.js';

test('useCounter', () => {
  const {count, increment} = useCounter()
  expect(count.value).toBe(0);

  increment();
  expect(count.value).toBe(1);
});

如果 composable 牽涉到元件 lifecyle hooks 或是 provide/inject 時,需要依附一個元件進行測試。

// test-utils.js
import {createApp} from 'vue';

export function withSetup(composable) {
  let result;

  const app = createApp({
    setup() {
      result = composable();
      // suppress missing template warning
      return () => {};
    },
  });

  app.mount(document.createElement('div'));
  // return the result and the app instance
  // for testing provide / unmount
  return [result, app];
};
// counter.js
import {ref, onMounted} from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;
  
  onMounted(() => {
    increment();
  });

  return {
    count,
    increment,
  };
};

result是 composable 的回傳值(return),測試裡面 app.mount();,將執行 onMounted

// counter.test.js
import {withSetup} from './test-utils';
import {useCounter} from './counter.js'

test('useCounter', () => {
  const [result, app] = withSetup(() => useCounter());

  // run assertions
  expect(result.count.value).toBe(0);

  // trigger onMounted hook if needed
  app.mount();

  expect(result.count.value).toBe(1);
});


另外還有一個常犯的錯誤,先看 composable 程式碼,有一個 computed 使用 formatFn 轉換 list:

// useListFormatter.js
import {computed, unref} from 'vue';

export function useListFormatter(list, formatFn) {
  const formattedList = computed(() => (
    unref(list).map(item => formatFn(item))
  ));

  return {
    formattedList,
  };
};

測試時需要注意回傳值被使用之前,formattedList 不會被執行。

// useListFormatter.test.js
import {useListFormatter} from './useListFormatter.js';

test('useListFormatter', () => {
  const list = ['a', 'b'];
  const formatFn = jest.fn();

  formatFn.mockImplementation(value => `new-${value}`)

  const {formattedList} = useListFormatter();
  
  // Don't do it! formatFn won't be excuted until formattedList is called.
  // expect(formatFn).toHaveBeenCalled();
  
  expect(formatFn).not.toHaveBeenCalled();

  expect(formattedList.value).toEqual(['new-a', 'new-b']);
  expect(formatFn).toHaveBeenCalledTimes(2);
  expect(formatFn).toHaveBeenNthCalledWith(1, 'a');
  expect(formatFn).toHaveBeenNthCalledWith(2, 'b');
});


參考資料:

https://vuejs.org/guide/scaling-up/testing.html#testing-composables


2022年3月5日 星期六

測試套件 Jest mock 基礎概念及 spyOn, fn 範例程式

當測試的對象有使用其他引用(import)時,我們希望能做為控制變因,設法聚焦測試的對象。Jest是常用的js單元測試套件,我們會 mock 方法spyOnfn來達成這個目的,Jest 安裝可以參考「[JavaScript] 安裝Jest單元測試」,底下說明幾種常用的情境。

jest.spyOn(object, methodName)

1. [基本用法] 監聽
單純測試某個method被呼叫的次數,或(官方說明),舉例下面範例是輸入兩個數字,並隨機輸出這個範圍內的數字。
// getRandonInt.js

export const getRandonInt = (min, max) => (
    Math.floor(Math.random(max) * (max - min) + min)
);
例如 Math.floor
jest.spyOn(Math, 'floor')
同理 Math.random ,所以可以將測試寫成:
import {getRandonInt} from './getRandonInt.js';

test('Should get random number between input min and max', () => {
    const spyFloor = jest.spyOn(Math, 'floor');
    const spyRandom = jest.spyOn(Math, 'random');

    const result = getRandonInt(1, 100);

    expect(result).toBeGreaterThanOrEqual(1);
    expect(result).toBeLessThanOrEqual(100);

    expect(spyFloor).toHaveBeenCalledTimes(1);

    expect(spyRandom).toHaveBeenCalledTimes(1);
    expect(spyRandom).toHaveBeenCalledWith(100);
});

因此可看到我們監聽的兩個method,第一 toHaveBeenCalledTimes 分別被呼叫的次數,及 toHaveBeenCalledWith 傳入的參數。


2. [進階] 替換回傳值
改變某個method的回傳值,以下面的範例是希望輸入一個值後,可以隨機產生一個數字並得到兩者相乘的結果
// multiplyNumber.js

export const multiplyNumber = num => (
    Math.random(10) * num
);
如果希望控制隨機產生的數字,可以使用 mockImplementation
import {multiplyNumber} from './multiplyNumber.js';

test('Should get the multiple of random number and input num', () => {
    const spyRandom = jest.spyOn(Math, 'random')
        .mockImplementation(() => 5);

    expect(Math.random(10)).toBe(5);
    expect(multiplyNumber(1000)).toBe(5000);

    spyRandom.mockRestore();

    expect(Math.random(10)).not.toBe(5);
});
可以理解成實際我們已經將 Math.random 變成:
Math['random'] = () => 5;

// you could definitely get parameter when method was called
// so implementation would be like ...
// Math['random'] = a => (a + 1);

mockRestore()可以恢復原本method的功能,因此直到我們呼叫 mockRestore()為止,無論我們呼叫幾次 Math.random ,都會按照我們的設定回傳數字 5。


假設希望這個設定是一次性的,則可以改用 mockImplementationOnce,這個寫法比較不容易出錯,是比較推薦的用法。

jest.spyOn(Math, 'random')
    .mockImplementationOnce(() => 5);
    .mockImplementationOnce(() => 20);

expect(Math.random(10)).toBe(5);
expect(Math.random(10)).toBe(20);


jest.fn(implementation)

用法和 spyOn非常類似,可以用來驗證呼叫次數及傳入參數等

const mockFn = jest.fn();

mockFn(100);

expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(100);
驗證的部分,toHaveBeenCalledTimestoHaveBeenCalledWith這兩個還有另一個寫法:
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn.mock.calls.length).toBe(1);

expect(mockFn).toHaveBeenCalledWith(100);
expect(mockFn.mock.calls[0][0]).toBe(100);

calls是一個陣列,長度等同於呼叫的次數,陣列內每一個位置也都是一個陣列,每個位置則對應到呼叫時的參數內容。可以實際印出得到 calls = [[100]],所以calls[0][0]代表的是第一次呼叫時第一個參數是什麼


依照 multiplyNumber.js 的範例,可以使用fn達到同樣的效果,測試結束前也要記得呼叫mockRestore免得影響其他測試‧。

const spyRandom = jest.fn(() => 5);
Math['random'] = spyRandom;
const spyRandom = jest.fn()
    .mockImplementation(() => 5);
Math['random'] = spyRandom;
const spyRandom = jest.fn()
    .mockReturnValueOnce(5);
Math['random'] = spyRandom;


jest.mock(module)

當測試有引入(import)其他模組、函式時,可以使用mock來替換回傳內容,我們稍微改寫剛剛的 multiplyNumber

import {getRandonInt} from './combineArray.js';

export const multiplyNumber = (min, max, num) => (
    getRandonInt(min, max) * num
);

為了清楚此時的getRandonInt已經被替換掉,我們刻意重新命名,在測試裡將回傳值設定為數字 20

import {getRandonInt as mockGetRandonInt} from './getRandonInt';
import {multiplyNumber} from './multiplyNumber';

jest.mock('./getRandonInt');

test('Should get the multiple of random number and input num', () => {
    mockGetRandonInt.mockReturnValueOnce(20);

    expect(multiplyNumber(1, 2, 3)).toBe(60);

    expect(mockGetRandonInt).toHaveBeenCalledTimes(1);
    expect(mockGetRandonInt).toHaveBeenCalledWith(1, 2);
});

另外可以利用requireActual可以取得原始的功能,當我們只需要部份取代時,可以藉此替換部分的內容,其餘皆按照原設定

const {getRandonInt} = jest.requireActual('../src/libs/combineArray');

mockGetRandonInt.mockImplementationOnce((a, b) => a + b);

expect(getRandonInt(1, 2)).toBeLessThanOrEqual(2);

expect(mockGetRandonInt(1, 2)).toBe(3);
expect(mockGetRandonInt).toHaveBeenCalledTimes(1);

最後一個重點,建立和初始化一個class的物件時,可以透過instances來得到建立的物件,它是一個陣列,會依照建立順序放在對應的位置上。

const myMock = jest.fn();

const a = new myMock();

expect(a).toBe(myMock.mock.instances[0]);

參考資料:



2022年1月25日 星期二

js 單元測試套件 Macha+Chai (es6 語法 Babel 設定)

安裝

首先安裝 mocha (官網)和 chai (官網)

npm install mocha chai --save-dev

chai 是一種斷言庫(Assertion Library),mocha 不像是jest已經內建,可以自行決定要使用哪一種套件,chai是其中最常見的一種。

package.json加入測試指令

"scripts": {
  "test": "npx mocha"
}


設定 Babel

安裝 Babel,再來的說明都會直接使用es6的語法,如果不需要,可以略過這部分。

npm install @babel/core @babel/preset-env @babel/register --save-dev

接著在根目錄新增 babel.config.js

// babel.config.js

module.exports = {
    presets: [
        '@babel/preset-env',
    ],
};

接著在根目錄新增 .mocharc.jsspec是設定 mocha 執行測試的檔案,預設為 test/ ,這裡設定成自動讀取 src/test/ 底下所有以 spec.js 為結尾的檔案。

require可以接陣列設定所需的 module

// .mocharc.js

module.exports = {
    spec: ['src/test/*.spec.js'],
    require: ['@babel/register'],
};

.mocharc.js 是 mocha 執行時會自動讀取的設定檔,也支援json 或 yaml(說明),可以選擇喜歡的格式新增,設定檔的內容可以參考:https://github.com/mochajs/mocha/tree/master/example/config

設定參考資料:
https://github.com/mochajs/mocha-examples/tree/master/packages/babel



建立測試

新增一個簡單的測試

// src/test/array.spec.js

import chai from 'chai';

describe('Array', () => {
  describe('#indexOf()', () => {
    it('should return -1 when the value is not present', function() {
      chai.assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});

馬上執行試試看

npm run test

應該會看到下面的資訊,

> vue3-webpack5-template@1.0.0 test
> npx mocha


  Array
      #indexOf()
          should return -1 when the value is not present

1 passing (8ms)

完成第一個測試囉!



支援 async/await

請安裝套件

npm install @babel/plugin-transform-runtime --save-dev

把套件加到 babel.config.js的 plugin 設定

module.exports = {
    require: ['@babel/register'],
    plugins: ['@babel/plugin-transform-runtime'],
};


Root Hooks

mocha 提供的 hook 包含 before(), after(), beforeEach(), and afterEach(),從語意應該不難看出用途,前兩個只會執行一次,後兩個則是每項測試都會執行。

若要在所有的測試上加上 Hook,則是有這四種 beforeAll(), afterAll(), beforeEach(), and afterEach(),執行時機不變,請根據情境加上執行內容:

// src/test/hooks.js

export const mochaHooks = {
  beforeEach: () => {
    // do something before every test

  }
};

接著在 .mocharc.js 加上設定

module.exports = {
    spec: ['src/test/*.spec.js'],
    require: [
      '@babel/register',
      'src/test/hooks.js',
    ],
};

https://mochajs.org/#hooks
https://mochajs.org/#defining-a-root-hook-plugin



測試結果

mocha 是個可愛的名稱,測試結果也可以換成很多有趣的模式:

npx mocha --report landing

https://mochajs.org/#reporters



支援瀏覽器介面

加上指定的路徑執行下面的指令

npx mocha init [PATH]
  • index.html
  • mocha.css
  • mocha.js
  • test.spec.js

目錄底下會多了上述這些檔案,此時只要將 test.spec.js 加入你的測試,再重新整理 index.html,就能看到測試結果已經輸出在畫面上。


可以將 chai 加入 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Mocha</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="mocha.css" />
    <script src="https://unpkg.com/chai/chai.js"></script>
  </head>
  <body>
    <div id="mocha"></div>
    <script src="mocha.js"></script>
    <script>
      mocha.setup('bdd');
    </script>
    <script src="tests.spec.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

再來可以直接將前面新增的 src/test/array.spec.js 內容貼過去,index.html 已經載入 mocha 和 chai ,記得把第一行的 import 拿掉。



2020年2月27日 星期四

[JavaScript] 安裝Jest單元測試


Jest為javascript的前端測試工具,安裝快速且不需複雜的設定,執行速度快,所有測項可以同時進行讓效能最大化,並可以搭配用在vue.js、React等進行UI component的測試。

安裝指令

npm install --save-dev jest
npm的安裝教學可以參考「教學」。


實作

以簡單的JavaScript程式來測試執行,將傳入的陣列合併成字串回傳:
// combineArray.js
const combineArray = (array, separator = ',') => {
        if (!Array.isArray(array)) {
                return new Error('Invalid input array');
        }
        if (typeof separator !== 'string') {
                return new Error('Invalid input separator');
        }

        return array.join(separator)
};

module.exports = combineArray;
測試的副檔名 *.test.js
// combineArray.test.js
const combineArray = require('./combineArray');

test('Combine array: [1, 2]', () => {
        expect(combineArray([1,2])).toBe('1,2');
});
  • 每個一測試檔案都必須至少有一個test(),第一個參數是名稱;第二個參數是執行expect實際測試內容的function。[參考]
  • expect()常用的方式expect(A).toBe(B),也就是期望A會等於B。[參考]

package.json新增內容:
{
  "scripts": {
    "test": "jest"
  }
}

執行測試

npm run test
所有的*.test.js都會跑過一次,並顯示結果,包含有多少測試項目以及成功和失敗的數量。



根據程式碼的複雜程度可以寫出許多測試項目,為了方便辨識測試內容,describe()可以將測項分類、建立群組,.toThrow()判斷錯誤發生的情形等等:
const combineArray = require('./combineArray');

describe('Combine array', () => {
        const inputArray = [1, 2];

        test('Default', () => {
                expect(combineArray([])).toBe('');
                expect(combineArray(inputArray)).toBe('1,2');
                expect(combineArray(inputArray, undefined)).toBe('1,2');
        });
        test('With custom separator', () => {
                expect(combineArray(inputArray, ' ')).toBe('1 2');
                expect(combineArray(inputArray, '*')).toBe('1*2');
                expect(combineArray(inputArray, '-')).toBe('1-2');
        });

        describe('With invalid input', () => {
                test.each(
                        [undefined, null, 1, 'string', {}]
                )('Input array: %s', (item) => {
                        expect(() => {
                                combineArray(item);
                        }).toThrow(new Error('Invalid input array'));
                });
                test.each(
                        [null, 1, [], {}]
                )('Input separator: %s', (item) => {
                        expect(() => {
                                combineArray(inputArray, item);
                        }).toThrow(new Error('Invalid input separator'));
                });
        });
});
執行結果


若有任何不符合預期的結果,console上會寫出哪一個測項失敗,預期的輸出和輸入為何。測試項目寫得越完備越好,往後即使更新主程式,也可以再以指令執行測試,確保程式運作如你所預期。

更多的方法可以前往官網參考:
https://jestjs.io/