当前位置: 首页>后端>正文

jest测试react路由

本节内容导图

  • react的路由
    • route测试
    • location测试
    • params测试
    • 根路由测试
      • useRoutes方法
      • matchRoutes方法
  • 按照config运行

react的路由

路由主要是匹配路径,接着就是做测试,以及匹配路由过程中的一些注意事项。可以理解为路由测试是建立在组件测试之上的一个测试,因为路由测试可以包括组件那些测试,同样,我们也是使用案例来一一讲解。

route测试

根据react-router官方给出的解答,我们需要使用到MemoryRouter,它不依赖于外部源:
https://reactrouter.com/en/6.8.2/router-components/memory-router

创建项目之前我们建立了三个路由,前面已经用了home和detail,这次我们用profile。

我们新建测试文件 __tests__/react/route/route.test.tsx

import Profile from "src/page/profile/profile";
import { MemoryRouter,Routes,Route } from "react-router-dom";
import { screen,render } from "@testing-library/react";
describe('test router',() => {
    // 如果需要去掉警告,可以加上这个
    // let consoleWarn: jest.SpyInstance;
    // beforeEach(() => {
    //     // eslint-disable-next-line @typescript-eslint/no-empty-function
    //     consoleWarn = jest.spyOn(console, "warn").mockImplementation(() => {});
    // });

    // afterEach(() => {
    //     consoleWarn.mockRestore();
    // });
    it('test route',() => {
        const Com = (
            <MemoryRouter basename="/profile" initialEntries={['/profile']}>
                <Routes>
                    <Route path="/" element={<Profile />}></Route>
                    // <Route path="/" element={<div>profile123</div>}></Route>
                </Routes>
            </MemoryRouter>
        )
        const container = render(Com);
        expect(container).toMatchSnapshot('route-test')
    })
})

解释:

  • 我们使用MemoryRouter去包裹路由Route,他有一个匹配路径initialEntries和一个基本路径basename,需要注意了,页面实际访问的路径为(path),但是这个path需要符合你定义的路由规范,即需要路由匹配,规则为:path = initialEntries - basename
  • 当路由不匹配的时候,测试仍旧会通过,但是会出现一个警告:提示我们路由匹配失败,element渲染为空。此时如果想去掉警告,那么就mock掉console的warn,让他们方位空即可.
  • 路由不会重新匹配,即,如果我们后面又重新匹配相同的路由/profile: profile123}>此时渲染的还是组件,而不是profile123

location测试

我们访问profile页面的时候,默认打印一次location的值为:

{
      pathname: '/profile',
      search: '',
      hash: '',
      state: null,
      key: 'default'
 }

咱们新建一个测试i文件 __tests__/react/route/route-location.test.tsx

import Profile from "src/page/profile/profile";
import { MemoryRouter,Routes,Route } from "react-router-dom";

import { screen,render } from "@testing-library/react";

describe('test router',() => {
    it('test route',() => {
        const location = {
            pathname: "/home",
            search: "",
            hash: "",
            state: null,
            key: "r9qntrej",
        };
        const Com = (
            <MemoryRouter initialEntries={['/profile/child1/123']}>
                <Routes location={location}>
                    <Route path="/profile/child1/:id" element={<Profile />}></Route>
                </Routes>
            </MemoryRouter>
        )
        const container = render(Com);
        expect(container).toMatchSnapshot('route-test')
    })
})

在这里,需要注意:location优先级高于initialEntries,也就是说,虽然path匹配了路径/profile/child1/:id,但实际匹配路径是location中的pathname,而pathname=home,这个路径在initialEntries中找不到,因此匹配失败;

const Com = (
    <MemoryRouter initialEntries={['/profile/child1/123']}>
        <Routes>
            <Route path="/profile/child1/:id" element={<ProfileChild1 />}></Route>
            <Route path="/profile/child2" element={<ProfileChild2 />}></Route>
        </Routes>
    </MemoryRouter>
)

另一个是这种情况,initialEntries指定了一个路由,而Routes里面却有两个需要匹配的路由,此时测试可以通过,但是引入的/profile/child2路径无法渲染,同时还导致覆盖率降低了


jest测试react路由,第1张
覆盖率图

因此,最好一个initialEntries对应一个路由,别想其他的就好。

const Com = (
    <MemoryRouter basename="/profile" initialEntries={['/profile/child1/1234?username=jack&age=12']}>
        <Routes>
            <Route path="/child1/:id" element={<ProfileChild1 />}></Route>
        </Routes>
    </MemoryRouter>
)

我们只需要正常的写search即可,这样我们就能够在页面中通过useLocation拿到对应的值。同理,如果我们想传state,也是一样,但是我们需要将location传给Routes

it('test location',() => {
    const location = {
        pathname: "/profile/child1/1234",
        search: "",
        hash: "",
        state: {
            formDD: {
                username: 'jack',
                age: 23
            }
        },
        key: "r9qntrej",
    };    
    const Com = (
        <MemoryRouter initialEntries={['/profile/child1/123']}>
            <Routes location={location}>
                <Route path="/profile/child1/:id" element={<ProfileChild1 />}></Route>
            </Routes>
        </MemoryRouter>
    )
    const container = render(Com);
    expect(container).toMatchSnapshot('route-test')
})

因此,请记住:对于某些页面他们是有search值或者state值的,我们就可以使用这种方法去测试

params测试

测试之前先把我们的根路由加点东西

我们给profile加一个child

{ 
    path: "/profile", 
    element: <Outlet />,
    children: [
      {
        path: '',
        element: <Profile />
      },
      {
        path: 'child1/:id',
        element: <ProfileChild1 />
      },
      {
        path: 'child2',
        element: <ProfileChild2 />
      }
    ]  
  },

同时新建这两个页面,路径是这样子的


jest测试react路由,第2张
此时我的子路由页面情况

在页面 page/profile/Com/p-child1.tsx

import { useParams } from 'react-router-dom';
export default function ProfileChild1() {
    const params = useParams();
    console.log(params,'params')
    return (
        <div>
            <p>profile child1</p>
            <p>params: { params.id }</p>
        </div>
    )
}

然后是,测试文件 __tests__/react/route/route-params.test.tsx

import Profile from "src/page/profile/profile";
import ProfileChild1 from "src/page/profile/child/child1/p-child1";
import ProfileChild2 from "src/page/profile/child/child2/p-child2";
import { MemoryRouter,Routes,Route } from "react-router-dom";
import { screen,render } from "@testing-library/react";

describe('test params',() => {
    it('test route',() => {
        const Com = (
            <MemoryRouter basename="/profile" initialEntries={['/profile/child1/1234']}>
                <Routes>
                    <Route path="/child1/:id" element={<ProfileChild1 />}></Route>
                </Routes>
            </MemoryRouter>
        )
        const container = render(Com);
        expect(container).toMatchSnapshot('route-params-test')
    })
})

快照图


jest测试react路由,第3张
快照图

根路由测试

根路由测试,即测试router.tsx

useRoutes方法

我们一个项目至少有一个router文件夹,并导出一个根路由,最终会挂在到app上,那么这个根路由怎么测试呢?这里我用的是useRoutes创建的根路由,因此也是用它来测试路由

我们新建文件 __tests/react/route-routes.test.tsx

import { routes } from "src/router";
import * as TestRenderer from "react-test-renderer";
import type { RouteObject } from "react-router";

import { MemoryRouter,useRoutes } from "react-router-dom";

describe('test router',() => {
    it('test routes',() => {
        const renderer: TestRenderer.ReactTestRenderer = (
            TestRenderer.create(
                <MemoryRouter>
                    <RoutesRenderer routes={routes} />
                </MemoryRouter>
            )
        )
        expect(renderer).toMatchSnapshot()
    })
})
function RoutesRenderer({
    routes,
    location,
}: {
    routes: RouteObject[];
    location?: Partial<Location> & { pathname: string };
}) {
    return useRoutes(routes, location);
}

matchRoutes方法

matchRoutes针对给定的一组路由运行路由匹配算法,location以查看哪些路由(如果有)匹配。如果找到匹配项,RouteMatch则会返回一组对象,每个匹配的路由对应一个对象路由的测试,会发现我们只需要用到了他,并且测试通过了就能覆盖到。

他的源码为:

export declare function matchRoutes<
    RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject
>(
    routes: RouteObjectType[], 
    locationArg: Partial<Location> | string, 
    basename?: string
): AgnosticRouteMatch<string, RouteObjectType>[] | null;

可以看到它传入三个参数,一个是路由对象,另一个是匹配路径,以及一个基本路径。返回的是一个关于路由路径的字符串类型数组

import { routes } from "src/router";
import * as TestRenderer from "react-test-renderer";
import type { RouteObject } from "react-router";

import { MemoryRouter,useRoutes,matchRoutes } from "react-router-dom";

function pickPaths(routes: RouteObject[], pathname: string): string[] | null {
    const matches = matchRoutes(routes, pathname);
    return matches && matches.map((match) => match.route.path || "");
}

describe('test router',() => {
    it('test routes',() => {
        const res = pickPaths(routes, "/profile");
        console.log(res,'--=-=-')
    })
})

两种方法都可以,按照个人喜好使用。

按照config运行

前面组件的测试,以及现在的路由测试我们新建了很多文件,目前这些文件都保存在tests/react/**下面,那么我们总不能每一次都只运行一个文件,或者直接jest全部运行,能不能只运行部分文件?答:可以,以配置的方式运行,这里我们把配置文件已经改成了ts文件类型。

目的:运行react文件夹下面的测试文件

新建文件 __tests__/react/config/test.config.ts

// 配置类型
import type { Config } from '@jest/types'
// 导入默认的配置
import jestConfig from '../../../../jest.config';

export default {
    // 对象合并
    ...jestConfig,
    // 根路径
    rootDir: '../../../..',
    // 收集的文件类型,这里收集了react下面的全部文件,以及page下面的全部文件。这样在测试成功之后,如果我们加上了覆盖率检测,那么这些文件都会覆盖到
    collectCoverageFrom: [
        ...(jestConfig.collectCoverageFrom as Array<string>),
        '!**/*.(ts|tsx)',
        '**/__tests__/react/*.test.tsx',
        '**/page/*.tsx',
        '**/page/**/*.tsx'
    ],
    // 测试文件,这里测试的是__tests__/react下面的全部测试文件
    testMatch: [
        '**/src/__tests__/react/*.test.tsx',
        '**/src/__tests__/react/**/*.test.tsx'
    ]
} as Config.InitialOptions

默认的配置我也改动了一点点,这里贴一下

import type { Config } from '@jest/types'

export default {
  // 在每次测试之前自动清除模拟
  clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // Indicates which provider should be used to instrument code for coverage
  coverageProvider: "v8",

  roots: [
    "<rootDir>/src/"
  ],
  // 这里的将一些不需要的文件剔除掉
  collectCoverageFrom: [
    '**/*.(tsx|ts)',
    '!**/node_modules/**',
    '!**/coverage/**',
    '!**/*.d.ts'
  ],


  // A preset that is used as a base for Jest's configuration
  preset: 'ts-jest',
  // An array of directory names to be searched recursively up from the requiring module's location
  moduleDirectories: [
    "node_modules",
    "src"
  ],
  modulePaths: ['<rootDir>'],
  moduleNameMapper: {
    '\.(css|scss|less)': 'identity-obj-proxy',
    '\.(jpg|png|webp|gig|svg|mp4|webm|mp3|m4a|aac)$': 'identity-obj-proxy',
    '^~/(.*)': '<rootDir>/src/'
  },
  testEnvironment: 'jsdom',
} as Config.InitialOptions

然后我们就可以使用下面的命令跑通:

jest --config='./src/__tests__/react/config/test.config.ts' -u --coverage

测试结果

jest测试react路由,第4张
多文件一同测试

注意事项:
  • rootDir指向一定是根路径,即你的项目路径,否则会报错
  • 当我们不想测试某一个文件的时候,在testMatch直接把某个测试或者某些测试剔除
testMatch: [
    '**/src/__tests__/react/*.test.tsx',
    '**/src/__tests__/react/**/*.test.tsx',
    '!**/src/__tests__/react/route/route-routes.test.tsx',
    // '!**/src/__tests__/react/route/route-matchRoutes.test.tsx',
]

这便是按照配置运行了,再结合我们很早之前讲解到的cli配置(
https://www.toutiao.com/article/7251203760847536698/),那么我们就清楚了单个文件运行测试、多个文件测试以及整体测试,三种方法。以上,便是路由的全部,源码有需要可以从这里获取:https://gitee.com/xifeng-canyang/jest-copy-file-and-video/tree/master/src/__tests__/react/route。


https://www.xamrdz.com/backend/3d61926718.html

相关文章: