أريد أن أتأكد من أن المكون الخاص بي يتم عرضه بشكل صحيح باستخدام خطاف useRouter (في الواقع أحاول فهم كيفية عمل التوجيه الديناميكي الجديد) ، لذلك لدي رمز:
import React from 'react';
import { NextPage } from 'next';
import { useRouter } from 'next/router';
const UserInfo : NextPage = () => {
const router = useRouter();
const { query } = router;
return <div>Hello {query.user}</div>;
};
export default UserInfo;
وما أحاول هو:
// test
import { render, cleanup, waitForElement } from '@testing-library/react';
import UserInfo from './$user';
// somehow mock useRouter for $user component!!!
afterEach(cleanup);
it('Should render correctly on route: /users/nikita', async () => {
const { getByText } = render(<UserInfo />);
await waitForElement(() => getByText(/Hello nikita!/i));
});
لكني تلقيت خطأ TypeError: Cannot read property 'query' of null
يشير إلى سطر const router = useRouter();
.
ملاحظة: أعلم أن التوجيه الديناميكي متاح في إصدارات الكناري فقط في الوقت الحالي وقد يتغير ، لكني واجهت مشكلة مع جهاز التوجيه ، وليس مع ميزة ويب (هل أنا؟).
مرحبًا ، لا تزال هذه الميزة تجريبية ولكن useRouter
يستخدم React.useContext
لاستهلاك السياق من next-server/dist/lib/router-context
. للسخرية منه ، ستحتاج إلى لفه في موفر السياق من هناك على غرار هذا السطر
ijjk مرحبا ، شكرا لك!
لا أعرف ما إذا كنت أفعل ذلك بشكل صحيح ، لكن الاختبار يجتاز 😂
import { render, cleanup, waitForElement } from '@testing-library/react';
import { createRouter } from 'next/router';
import { RouterContext } from 'next-server/dist/lib/router-context';
const router = createRouter('', { user: 'nikita' }, '', {
initialProps: {},
pageLoader: jest.fn(),
App: jest.fn(),
Component: jest.fn(),
});
import UserInfo from './$user';
afterEach(cleanup);
it('Should render correctly on route: /users/nikita', async () => {
const { getByText } = render(
<RouterContext.Provider value={router}>
<UserInfo />
</RouterContext.Provider>,
);
await waitForElement(() => getByText(/Hello nikita!/i));
});
إذا كانت هناك طريقة أكثر تجريدية لسخرية معلمات الاستعلام ، لذلك سأكون قادرًا على تمرير المسار الفعلي ( /users/nikita
على سبيل المثال) وتمرير المسار إلى الملف؟ ماذا تعتقد؟
قد يكون من الأفضل السخرية من جهاز التوجيه مباشرةً بدلاً من استدعاء createRouter
نظرًا لأن واجهة برمجة التطبيقات هذه داخلية ويمكن تغييرها في أي وقت. هذا مثال:
import React from 'react'
import { render } from '@testing-library/react'
import { RouterContext } from 'next-server/dist/lib/router-context'
describe('Basic test', () => {
it('Renders current user value', async () => {
const router = {
pathname: '/users/$user',
route: '/users/$user',
query: { user: 'nikita' },
asPath: '/users/nikita',
}
const User = require('../pages/users/$user').default
const tree = render(
<RouterContext.Provider value={router}>
<User />
</RouterContext.Provider>
)
expect(tree.getByText('User: nikita')).toBeTruthy()
})
})
ijjk هذا منطقي. شكرا جزيلا!
هل هناك أي طريقة للسخرية من useRouter باستخدام Enzyme + Jest؟ لقد كنت أبحث على الإنترنت قليلاً والنتائج ذات الصلة الوحيدة التي ظهرت هي هذه المشكلة.
تمكنت من السخرية منه بهذه الطريقة.
import * as nextRouter from 'next/router';
nextRouter.useRouter = jest.fn();
nextRouter.useRouter.mockImplementation(() => ({ route: '/' }));
يعمل لدي أيضًا jest.spyOn
-
import React from 'react'
import { render } from '@testing-library/react'
import ResultsProductPage from 'pages/results/[product]'
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe('ResultsProductPage', () => {
it('renders - display mode list', () => {
useRouter.mockImplementationOnce(() => ({
query: { product: 'coffee' },
}))
const { container } = render(
<ResultsProductPage items={[{ name: 'mocha' }]} />
)
expect(container).toMatchSnapshot()
})
})
انتهى بي الأمر بالسخرية من الأمر على هذا النحو ، فأنا بحاجة فقط إلى تصدير useRouter
لذلك نجح هذا بشكل كافٍ لتحقيق أغراضي:
jest.mock("next/router", () => ({
useRouter() {
return {
route: "/",
pathname: "",
query: "",
asPath: "",
};
},
}));
إذا كان أي شخص هنا يتطلع إلى السخرية من useRouter
ببساطة لتجنب التدخل من أمر حتمي prefetch
، فعندئذ ستعمل هذه الوهمية البسيطة الميتة
jest.mock("next/router", () => ({
useRouter() {
return {
prefetch: () => null
};
}
}));
مثال على حالة الاستخدام هو مكون نموذج يتضمن شيئًا مثل:
const router = useRouter();
useEffect(() => {
router.prefetch("/success");
if (confirmSuccess) {
doStuff();
router.push( {pathname: "/success" } )
}
}, [data]);
ijjk هل تغير هذا السلوك في الإصدار الأخير؟ اضطررت إلى الاستيراد من next/dist/next-server/lib/router-context
. لن يتعرف على السياق إذا قمت بتثبيت next-server
بشكل منفصل.
لدي المشكلة بالضبط نفس.
نحن أقل من 9. التالي لا يوجد أي من الحلول التي تستخدم RouterContext.Provider
تعمل بالفعل.
الطريقة الوحيدة لنجاحي في الاختبار هي استخدام حل useRouter
يكون دائمًا غير معرف.
هذا ليس مثاليًا لأنني لا أستطيع تعيين معلمات مختلفة للاختبار الخاص بي.
أي أفكار حول هذا؟
تعديل
لقد جعلتها تعمل مع محاكاة عالمية لاستيراد next/router
و spyOn
على المحاكاة ، مما يسمح لي بالاتصال بـ mockImplementation(() => ({// whatever you want})
في كل اختبار.
يبدو شيئًا مثل:
jest.mock("next/router", () => ({
useRouter() {
return {
route: "",
pathname: "",
query: "",
asPath: "",
};
},
}));
const useRouter = jest.spyOn(require("next/router"), "useRouter");
ثم في الاختبارات:
useRouter.mockImplementation(() => ({
route: "/yourRoute",
pathname: "/yourRoute",
query: "",
asPath: "",
}));
هذا ليس مثاليًا ولكنه يعمل بالنسبة لي على الأقل
FWIW هذا ما استقرت عليه:
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { action } from '@storybook/addon-actions'
import PropTypes from 'prop-types'
import { useState } from 'react'
import Router from 'next/router'
function RouterMock({ children }) {
const [pathname, setPathname] = useState('/')
const mockRouter = {
pathname,
prefetch: () => {},
push: async newPathname => {
action('Clicked link')(newPathname)
setPathname(newPathname)
}
}
Router.router = mockRouter
return (
<RouterContext.Provider value={mockRouter}>
{children}
</RouterContext.Provider>
)
}
RouterMock.propTypes = {
children: PropTypes.node.isRequired
}
export default RouterMock
كنت بحاجة إلى شيء نجح في كل من Storybook و Jest. يبدو أن هذا يفي بالغرض ، لقد قمت للتو بتعيين <Routermock>
مكان ما أعلى شجرة المكون. إنه ليس مثاليًا لأنني لا أحب تجاوز Router.router
باستمرار.
أعتقد أن الحل الرسمي الساخر سيكون رائعًا :)
نجحت طريقة mockImplementationOnce()
... إذا احتاج المكون الخاص بك إلى العرض أكثر من مرة أثناء الاختبار ، فستجد أنه لا يستخدم جهاز التوجيه الوهمي الخاص بك في العرض الثاني وسوف يفشل اختبارك. من الأفضل دائمًا استخدام mockImplementation()
بدلاً من ذلك ، إلا إذا كان لديك سبب محدد لاستخدام mockImplementationOnce()
.
اضطررت إلى مراجعة تطبيقي الأولي لأنني كنت بحاجة إلى حالة فريدة useRouter
على أساس اختبار تلو الآخر. أخذت صفحة من المثال المقدم من @ nterol24s وقم بتحديثها لتكون بمثابة وظيفة مساعدة يمكنني الاتصال بها في اختباراتي:
// Mocks useRouter
const useRouter = jest.spyOn(require("next/router"), "useRouter");
/**
* mockNextUseRouter
* Mocks the useRouter React hook from Next.js on a test-case by test-case basis
*/
export function mockNextUseRouter(props: {
route: string;
pathname: string;
query: string;
asPath: string;
}) {
useRouter.mockImplementationOnce(() => ({
route: props.route,
pathname: props.pathname,
query: props.query,
asPath: props.asPath,
}));
}
يمكنني الآن القيام بأشياء مثل:
import { mockNextUseRouter } from "@src/test_util";
describe("Pricing Page", () => {
// Mocks Next.js route
mockNextUseRouter({
route: "/pricing",
pathname: "/pricing",
query: "",
asPath: `/pricing?error=${encodeURIComponent("Uh oh - something went wrong")}`,
});
test("render with error param", () => {
const tree: ReactTestRendererJSON = Renderer.create(
<ComponentThatDependsOnUseRouter />
).toJSON();
expect(tree).toMatchSnapshot();
});
});
لاحظ تعليق mbrowne - mockNextUseRouter
و mockNextUseRouterOnce
إذا احتجت إلى ذلك.
أيضًا كبير: +1: لحل رسمي يسخر من timneutkens
بالنسبة لأي شخص يريد مثيل Router
الاستهزاء به عالميًا ، يمكنك وضع مجلد __mocks__
أي مكان واستهداف الحزمة next/router
كما يلي:
__mocks__/next/router/index.js
(يجب أن يتبع نمط بنية المجلد هذا!)
يستهدف هذا المثال أدناه Router.push
و Router.replace
:
jest.mock("next/router", () => ({
// spread out all "Router" exports
...require.requireActual("next/router"),
// shallow merge the "default" exports with...
default: {
// all actual "default" exports...
...require.requireActual("next/router").default,
// and overwrite push and replace to be jest functions
push: jest.fn(),
replace: jest.fn(),
},
}));
// export the mocked instance above
module.exports = require.requireMock("next/router");
الآن ، في أي مكان يوجد فيه import Router from "next/router";
، سيكون المثال السخرية. ستتمكن أيضًا من إضافة وظائف mockImplementation
عليها نظرًا لأنه سيتم السخرية منها عالميًا.
إذا كنت تريد إعادة تعيين هذا المثيل لكل اختبار ، فأضف خاصية jest.json
الخاص بك.
كمرجع ، إليك بنية Router
إذا كنت تريد استهداف تصدير معين:
{
__esModule: true,
useRouter: [Function: useRouter],
makePublicRouterInstance: [Function: makePublicRouterInstance],
default: {
router: null,
readyCallbacks: [
[Function],
[Function],
[Function],
[Function],
[Function],
[Function]
],
ready: [Function: ready],
push: [Function],
replace: [Function],
reload: [Function],
back: [Function],
prefetch: [Function],
beforePopState: [Function] },
withRouter: [Function: withRouter],
createRouter: [Function: createRouter],
Router: {
[Function: Router]
events: {
on: [Function: on],
off: [Function: off],
emit: [Function: emit]
}
},
NextRouter: undefined
}
}
بالإضافة إلى ذلك ، إذا كان عليك أن تستخدم مكونات mount
withRouter
أو useRouter
ولا تريد السخرية منهم ولكنك لا تزال تريد إنشاء بعض الاختبارات ضد / حول منهم ، ثم يمكنك الاستفادة من وظيفة مصنع المجمع HOC للاختبار:
import { createElement } from "react";
import { mount } from "enzyme";
import { RouterContext } from "next/dist/next-server/lib/router-context";
// Important note: The RouterContext import will vary based upon the next version you're using;
// in some versions, it's a part of the next package, in others, it's a separate package
/**
* Factory function to create a mounted RouterContext wrapper for a React component
*
* <strong i="33">@function</strong> withRouterContext
* <strong i="34">@param</strong> {node} Component - Component to be mounted
* <strong i="35">@param</strong> {object} initialProps - Component initial props for setup.
* <strong i="36">@param</strong> {object} state - Component initial state for setup.
* <strong i="37">@param</strong> {object} router - Initial route options for RouterContext.
* <strong i="38">@param</strong> {object} options - Optional options for enzyme's mount function.
* <strong i="39">@function</strong> createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root)
* <strong i="40">@returns</strong> {wrapper} - a mounted React component with Router context.
*/
export const withRouterContext = (
Component,
initialProps = {},
state = null,
router = {
pathname: "/",
route: "/",
query: {},
asPath: "/",
},
options = {},
) => {
const wrapper = mount(
createElement(
props => (
<RouterContext.Provider value={router}>
<Component { ...props } />
</RouterContext.Provider>
),
initialProps,
),
options,
);
if (state) wrapper.find(Component).setState(state);
return wrapper;
};
استخدام المثال:
import React from "react";
import withRouterContext from "./path/to/reusable/test/utils"; // alternatively you can make this global
import ExampleComponent from "./index";
const initialProps = {
id: "0123456789",
firstName: "John",
lastName: "Smith"
};
const router = {
pathname: "/users/$user",
route: "/users/$user",
query: { user: "john" },
asPath: "/users/john",
};
const wrapper = withRouterContext(ExampleComponent, initialProps, null, router);
...etc
لماذا تستخدم هذا؟ لأنه يسمح لك بالحصول على مكون React قابل لإعادة الاستخدام وملفوف في سياق جهاز التوجيه ؛ والأهم من ذلك ، أنه يسمح لك باستدعاء wrapper.setProps(..)
على مكون الجذر!
import { useRouter } from 'next/router'
jest.mock('next/router', () => ({
__esModule: true,
useRouter: jest.fn()
}))
describe('XXX', () => {
it('XXX', () => {
const mockRouter = {
push: jest.fn() // the component uses `router.push` only
}
;(useRouter as jest.Mock).mockReturnValue(mockRouter)
// ...
expect(mockRouter.push).toHaveBeenCalledWith('/hello/world')
})
})
لم يعمل أي من هذه الحلول بالنسبة لي. سير العمل "الصحيح" موصوف هنا أيضًا في مستندات Jest: https://jestjs.io/docs/en/es6-class-mocks#spying -on-methods-of-our-class
ومع ذلك ، أستطيع أن أرى الصورة وهمية ، لكنها لا تسجل المكالمات ...
هذا هو test-utils.tsx
الحالي الخاص بي. أنا أحب هذا أفضل بكثير من استخدام محاكاة عالمية.
import React from 'react';
import { render as defaultRender } from '@testing-library/react';
import { RouterContext } from 'next/dist/next-server/lib/router-context';
import { NextRouter } from 'next/router';
export * from '@testing-library/react';
// --------------------------------------------------
// Override the default test render with our own
//
// You can override the router mock like this:
//
// const { baseElement } = render(<MyComponent />, {
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
type DefaultParams = Parameters<typeof defaultRender>;
type RenderUI = DefaultParams[0];
type RenderOptions = DefaultParams[1] & { router?: Partial<NextRouter> };
export function render(
ui: RenderUI,
{ wrapper, router, ...options }: RenderOptions = {},
) {
if (!wrapper) {
wrapper = ({ children }) => (
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
{children}
</RouterContext.Provider>
);
}
return defaultRender(ui, { wrapper, ...options });
}
const mockRouter: NextRouter = {
basePath: '',
pathname: '/',
route: '/',
asPath: '/',
query: {},
push: jest.fn(),
replace: jest.fn(),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(),
beforePopState: jest.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
},
isFallback: false,
};
faybayer شكرا! يعمل بشكل رائع!
حل flybayer يناسبني ، ولكن علي تحديد نوع الإرجاع في وظيفة التصيير
import { render as defaultRender, RenderResult } from '@testing-library/react'
...
export function render(
ui: RenderUI,
{ wrapper, router, ...options }: RenderOptions = {}
): RenderResult { ... }
بالنسبة لأي شخص يريد مثيل
Router
الاستهزاء به عالميًا ، يمكنك وضع مجلد__mocks__
أي مكان واستهداف الحزمةnext/router
كما يلي:
__mocks__/next/router/index.js
(يجب أن يتبع نمط بنية المجلد هذا!)يستهدف هذا المثال أدناه
Router.push
وRouter.replace
:jest.mock("next/router", () => ({ // spread out all "Router" exports ...require.requireActual("next/router"), // shallow merge the "default" exports with... default: { // all actual "default" exports... ...require.requireActual("next/router").default, // and overwrite push and replace to be jest functions push: jest.fn(), replace: jest.fn(), }, })); // export the mocked instance above module.exports = require.requireMock("next/router");
الآن ، في أي مكان يوجد فيه
import Router from "next/router";
، سيكون المثال السخرية. ستتمكن أيضًا من إضافة وظائفmockImplementation
عليها نظرًا لأنه سيتم السخرية منها عالميًا.
إذا كنت تريد إعادة تعيين هذا المثيل لكل اختبار ، فأضف خاصيةjest.json
الخاص بك.كمرجع ، إليك بنية
Router
إذا كنت تريد استهداف تصدير معين:{ __esModule: true, useRouter: [Function: useRouter], makePublicRouterInstance: [Function: makePublicRouterInstance], default: { router: null, readyCallbacks: [ [Function], [Function], [Function], [Function], [Function], [Function] ], ready: [Function: ready], push: [Function], replace: [Function], reload: [Function], back: [Function], prefetch: [Function], beforePopState: [Function] }, withRouter: [Function: withRouter], createRouter: [Function: createRouter], Router: { [Function: Router] events: { on: [Function: on], off: [Function: off], emit: [Function: emit] } }, NextRouter: undefined } }
بالإضافة إلى ذلك ، إذا كان عليك أن تستخدم مكونات
mount
withRouter
أوuseRouter
ولا تريد السخرية منهم ولكنك لا تزال تريد إنشاء بعض الاختبارات ضد / حول منهم ، ثم يمكنك الاستفادة من وظيفة مصنع المجمع HOC للاختبار:import { createElement } from "react"; import { mount } from "enzyme"; import { RouterContext } from "next/dist/next-server/lib/router-context"; // Important note: The RouterContext import will vary based upon the next version you're using; // in some versions, it's a part of the next package, in others, it's a separate package /** * Factory function to create a mounted RouterContext wrapper for a React component * * <strong i="33">@function</strong> withRouterContext * <strong i="34">@param</strong> {node} Component - Component to be mounted * <strong i="35">@param</strong> {object} initialProps - Component initial props for setup. * <strong i="36">@param</strong> {object} state - Component initial state for setup. * <strong i="37">@param</strong> {object} router - Initial route options for RouterContext. * <strong i="38">@param</strong> {object} options - Optional options for enzyme's mount function. * <strong i="39">@function</strong> createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root) * <strong i="40">@returns</strong> {wrapper} - a mounted React component with Router context. */ export const withRouterContext = ( Component, initialProps = {}, state = null, router = { pathname: "/", route: "/", query: {}, asPath: "/", }, options = {}, ) => { const wrapper = mount( createElement( props => ( <RouterContext.Provider value={router}> <Component { ...props } /> </RouterContext.Provider> ), initialProps, ), options, ); if (state) wrapper.find(Component).setState(state); return wrapper; };
استخدام المثال:
import React from "react"; import withRouterContext from "./path/to/reusable/test/utils"; // alternatively you can make this global import ExampleComponent from "./index"; const initialProps = { id: "0123456789", firstName: "John", lastName: "Smith" }; const router = { pathname: "/users/$user", route: "/users/$user", query: { user: "john" }, asPath: "/users/john", }; const wrapper = withRouterContext(ExampleComponent, initialProps, null, router); ...etc
لماذا تستخدم هذا؟ لأنه يسمح لك بالحصول على مكون React قابل لإعادة الاستخدام وملفوف في سياق جهاز التوجيه ؛ والأهم من ذلك ، أنه يسمح لك باستدعاء
wrapper.setProps(..)
على مكون الجذر!
مرحبًا ، أتلقى هذا الخطأ:
TypeError: لا تعد وظيفة required.requireMock دالة
استخدم هذا الحل:
jest.mock("next/router", () => ({
// spread out all "Router" exports
...jest.requireActual("next/router"),
// shallow merge the "default" exports with...
default: {
// all actual "default" exports...
...jest.requireActual("next/router").default,
// and overwrite push and replace to be jest functions
push: jest.fn(),
replace: jest.fn(),
},
}));
// export the mocked instance above
module.exports = jest.requireMock("next/router");
التعليق الأكثر فائدة
انتهى بي الأمر بالسخرية من الأمر على هذا النحو ، فأنا بحاجة فقط إلى تصدير
useRouter
لذلك نجح هذا بشكل كافٍ لتحقيق أغراضي: