我正在尝试在我的 Nextjs 项目上设置单元测试。我已经按照official Nextjs docs上的文档进行设置。
我遇到的问题似乎与配置本身或
@headlessui/react
库及其导入方式有关。
当我尝试运行测试时,我收到以下错误消息:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) 但得到的是:对象。 检查
.的渲染方法Listbox
该组件在运行时和构建时工作得很好。
这是我的项目配置。
tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"removeComments": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
jest.config.mjs
import nextJest from 'next/jest.js';
const createJestConfig = nextJest({
dir: './',
});
/** @type {import('jest').Config} */
const config = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleDirectories: ['node_modules', '<rootDir>/'],
testMatch: ['**/*.(test|spec).(js|jsx|ts|tsx)'],
coveragePathIgnorePatterns: ['/node_modules/'],
};
export default createJestConfig(config);
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{ protocol: 'https', hostname: '**.pixabay.com' }],
},
reactStrictMode: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
};
module.exports = nextConfig;
__tests__/components
中的测试文件
import { render, screen } from '@testing-library/react';
import { ListType } from 'components/calculator/builder/utils/types/list';
import List from 'components/ui/fields/list/list';
jest.mock('components/calculator/context/hooks', () => ({
// ... mock custom hook.
}));
const mockList: ListType = {
// ... mock data
};
const renderComponent = () => {
render(<List list={mockList} />);
};
describe('List component', () => {
test('check if the correct list options are displayed', () => {
renderComponent();
expect(screen.getAllByRole('option')).toHaveLength(mockList.options.length);
expect(screen.getByText(mockList.description)).toBeInTheDocument();
expect(screen.getByText(mockList.placeholder)).toBeInTheDocument();
});
});
实际测试组件的片段
import { Listbox } from '@headlessui/react'; // <= the actual Listbox import from node_modules
// some other inner imports
interface ListProps {
list: ListType;
}
export default function List({ list }: ListProps) {
return (
<Listbox>
// ...implementation is irrelevant
</Listbox>)
}
package.json
{
"name": "cw-client",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"typescript": "tsc",
"test": "jest",
"test:ci": "jest --ci"
},
"engines": {
"node": ">=19.3.0"
},
"dependencies": {
"@dnd-kit/core": "^6.0.7",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@headlessui/react": "^1.7.7",
"@next/font": "13.1.1",
"lodash.debounce": "^4.0.8",
"mathjs": "^11.6.0",
"next": "13.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-number-format": "^5.1.4"
},
"devDependencies": {
"@next/eslint-plugin-next": "^13.1.1",
"@svgr/webpack": "^6.5.1",
"@tailwindcss/forms": "^0.5.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "18.11.17",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"autoprefixer": "^10.4.13",
"eslint": "8.30.0",
"eslint-config-next": "13.1.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"postcss": "^8.4.20",
"prettier": "2.8.1",
"prettier-plugin-tailwindcss": "^0.2.1",
"tailwindcss": "^3.3.1",
"typescript": "4.9.4"
}
}
我检查了资源,大多数时候它们与两个问题有关。
object
而不是未定义。所以这些对我的情况都没有帮助。我还看到这可能是使用路径别名时的问题,您可以将路径别名添加到 tsconfig.json 中的
path
并在 jest.config.json 中进行修改。但是因为我没有使用路径别名,所以我认为它不相关。
有趣的是,我尝试使用来自 Nextjs 样板的 repo 进行测试并且它有效。但是我的应用程序已经变得很大,可以重新开始了。
我尝试过的其他一些选择是:
export default
移动到底部。这些都没有帮助。
所以,我设法找到了根本原因。
问题并不像最初想象的那样出现在导入或别名中。问题是我正在使用
@svgr/webpack
将 .svg 文件转换为反应组件。
然而,这些不是 JSX 组件,它们在测试中抛出错误,因为 Jest 不知道如何解析 svg 导入。
对于遇到类似问题的任何人,这里是修复程序。
__mocks__/svg.tsx
。
import React, { SVGProps } from 'react';
const SvgrMock = React.forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
(props, ref) => <svg ref={ref} {...props} />
);
export const ReactComponent = SvgrMock;
export default SvgrMock;
jest.config.mjs
moduleNameMapper: {
'^.+\\.(svg)$': '<rootDir>/__mocks__/svg.tsx',
},
此配置解析导入到模拟 svg,如果有任何组件用于开玩笑,以便您可以测试功能。
我希望它能帮助遇到类似问题的任何人。