TypeScript + Jest + React-Testing-Library

纯Typescript测试相关配置

1: 安装jest相关的依赖
yarn add --dev jest @types/jest ts-jest

jest -- 我们的test runner, 测试断言库, mock库
@types/jest -- Jest的Typescript版
ts-jest -- 把Typescript编译为JavaScript

2:创建jest.config.js

在项目根目录下创建一个jest.config.js文件作为jest的配置文件,且添加以下内容

// jest.config.js
module.exports = {
    roots: ['<rootDir>/src'],
    transform: {
        '^.+\\.tsx?$': 'ts-jest',
    },
    testRegex: '^.+\\.test\\.(ts|tsx)$',
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
3: 添加跑测试的脚本
// package.json
"scripts": {
    "test": "jest",
      //....
  },

完成以上相关配置,我们就可以对一个Typescript纯函数进行测试了,例如以下例子:

//stringProcessor.ts
function reverseString(str: string) {
    return str.split('').reverse().join('');
}
export {reverseString}
//stringProcessor.test.ts
import {reverseString} from './stringProcessor';
test('', ()=> {
    expect(reverseString('hello')).toBe('olleh');
});

但是,如果我们需要测一个React component测试,我们需要借助另一个库Enzyme以及相关其他依赖。接下来,让我们来完成这部分的配置:

React测试相关配置

安装React测试相关的依赖
yarn add  --dev @testing-library/react @testing-library/react-hooks @testing-library/jest-dom

@testing-library/react 测试React Component的库
@testing-library/react-hooks 测试自己写的的React Hooks的库
@testing-library/jest-dom 提供更多利于dom测试的断言

往jest.config.js文件里面添加支持React component测试的相关配置
//jest.config.js
module.exports = {
    roots: ['<rootDir>/src'],
    setupFiles: ['<rootDir>/test.setup.js', '<rootDir>/test.shim.js'],
    transform: {
        '^.+\\.tsx?$': 'ts-jest',
    },
 
    moduleNameMapper: {
        '\\.(css|scss)': 'identity-obj-proxy', // mock 在react组件里import的CSS
        '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
            '<rootDir>/testMocks/assetsMocks.js', //mock 在react组件里import的图片
    },
    testRegex: '^.+\\.test\\.(ts|tsx)$',
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
在项目根目录添加testMocks/assetsMocks.js
//testMocks/assetsMocks.js
module.exports = '';

测试一个最简单的React组件

组件文件counter.tsx

//counter.tsx
import * as React from 'react';
import { useState } from 'react';

const Counter = () => {
    const [count, setCount] = useState(0);

    return (
        <div>
            <div data-testid='count-announcement'> you have been clicked {count} times</div>
            <button
                data-testid='increase-button'
                onClick={() => {
                    setCount(count + 1);
                }}
            >
                {' '}
                increase count
            </button>
        </div>
    );
};
export default Counter;

测试文件counter.test.tsx

//counter.test.tsx
import * as React from 'react';
import Counter from './counter';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

test('the count should be 1 when you click the increase button once', () => {
    // render
    const { getByTestId } = render(<Counter />);
    const increaseButton = getByTestId('increase-button');
    // act
    fireEvent.click(increaseButton);
    // assert
    expect(getByTestId('count-announcement')).toHaveTextContent('1');
});

测试一个有<Link>的React组件

组件文件HomePage.tsx

//HomePage.tsx
import React from 'react';
import logo from './images/logo.svg';
import pink from './images/pink.png';
import './HomePage.scss';
import { Link } from 'react-router-dom';

function App() {
    const name = `nana`;
    return (
        <div className='App'>
            <header className='App-header'>
                <img src={logo} className='App-logo' alt='logo' />
                <img src={pink} className='' alt='pink' />
                <Link to='/blogList'>Blog List Page</Link>
            </header>
            <p data-testid='name'>name: {name}</p>
        </div>
    );
}

export default App;

测试文件HomePage.test.tsx

//HomePage.test.tsx
import * as React from 'react';
import HomePage from './HomePage';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { BrowserRouter } from 'react-router-dom';

//This is testing demo to testing a react component with CSS and Images import
test('should render name correctly', () => {
    // render
    const { getByTestId } = render(
        //对于有Link的组件,在测试的时候,必须把它包到BrowserRouter里面
        <BrowserRouter>
            <HomePage />
        </BrowserRouter>,
    );
    const name = getByTestId('name');
    // act
    // assert
    expect(name).toHaveTextContent('nana');
});

这里需要特别注意的是: 对于有Link的组件,在测试的时候,必须把它包到BrowserRouter里面,不然JEST会不过。

测试一个有axios请求的组件

组件文件profile.tsx

//profile.tsx
import * as React from 'react';
import axios from 'axios';
import { useEffect, useState } from 'react';

const Profile = () => {
    const [isLoading, setIsLoading] = useState(true);
    const [username, setUsername] = useState('');
    const [errorMessage, setErrorMessage] = useState('');

    useEffect(() => {
        async function fetchData() {
            try {
                const response = await axios.get('https://my-json-server.typicode.com/pengmq/mock-server/profile', {});
                setIsLoading(false);
                setUsername(response.data.data.username);
            } catch (error) {
                setIsLoading(false);
                setErrorMessage(error);
            }
        }

        fetchData();
    }, []);

    return (
        <div>
            <div data-testid='error-message'>{errorMessage}</div>
            <div data-testid='loading-text'>
                <span>{isLoading ? 'loading....' : ''}</span>
            </div>
            <div data-testid='username'>{username}</div>
        </div>
    );
};
export default Profile;

测试文件profile.test.tsx

//profile.test.tsx
import * as React from 'react';
import { render, waitForElement } from '@testing-library/react';
import Profile from './profle';
import axios from 'axios';

//jest.mock(...)自动mock axios的API调用,这行代码必须放在文件最外层,不能放到test里面
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>; //This line is needed when work with typescript

test('loading text should hide and user name should show after get profile data successfully from server ', async () => {

    //mock axios的API返回,这行代码必须在render之前
    mockedAxios.get.mockResolvedValueOnce({ data: { data: { username: 'nana' } } });
    const { getByTestId, container } = render(<Profile />);
    // awaiting for sync function to be done with  await waitForElement()
    const [loadingText, username] = await waitForElement(() => [getByTestId('loading-text'), getByTestId('username')], {
        container,
    });
    //assert dom changes
    expect(loadingText).toHaveTextContent('');
    expect(username).toHaveTextContent('nana');
});

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容