λ΄ κ΅¬μ± μμκ° 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));
});
νμ§λ§ const router = useRouter();
λΌμΈμ κ°λ¦¬ν€λ TypeError: Cannot read property 'query' of null
μ€λ₯κ° λ°μν©λλ€.
μΆμ : λλ λμ λΌμ°ν μ΄ νμ¬ μΉ΄λ리μ λ²μ μμ μ¬μ© κ°λ₯νκ³ λ³κ²½λ μ μλ€λ κ²μ μκ³ μμ§λ§ WIP κΈ°λ₯μ΄ μλ λΌμ°ν°μ λ¬Έμ κ° μμ΅λλ€.
μλ
νμΈμ, μ΄ κΈ°λ₯μ μμ§ μ€νμ μ΄μ§λ§ 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
)λ₯Ό μ λ¬νκ³ νμΌ κ²½λ‘λ₯Ό μ λ¬ν μ μμ΅λκΉ? μ΄λ»κ² μκ°νλμ?
APIλ λ΄λΆμ μκ³ μΈμ λ μ§ λ³κ²½λ μ μμΌλ―λ‘ 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 μλ―Έκ° μμ΅λλ€. μ λ§ κ³ λ§μ!
Enzyme+Jestλ₯Ό μ¬μ©νμ¬ useRouterλ₯Ό μ‘°λ‘±νλ λ°©λ²μ΄ μμ΅λκΉ? λλ μ‘°κΈ λμ μ¨λΌμΈ κ²μμ ν΄ λ³΄μκ³ μ μΌνκ² κ΄λ ¨ κ²°κ³Όκ° λνλ¬μ΅λλ€. μ΄ λ¬Έμ μ λλ€.
μ΄λ° μμΌλ‘ μ‘°λ‘±νλ λ° μ±κ³΅νμ΅λλ€.
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
μ¬μ©νλ μ루μ
λ΄ ν
μ€νΈλ₯Ό ν΅κ³Όνλ μ μΌν λ°©λ²μ ν
μ€νΈ μμ μ μ κ°μ²΄λ‘ @aeksco μ루μ
μ μ¬μ©νλ κ²μ
λλ€. κ·Έλ μ§ μμΌλ©΄ 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
μ§μμ μΌλ‘ μ¬μ μνλ κ²μ μ’μνμ§ μκΈ° λλ¬Έμ μ΄μμ μ΄μ§ μμ΅λλ€.
곡μ λͺ¨μ μ루μ μ΄ μ’μ κ² κ°μμ :)
@smasontst μ λ°©λ²μ μ°λ¦¬μκ² ν¨κ³Όκ° μμμ§λ§ mockImplementationOnce()
μ£Όμνμμμ€ ... ν
μ€νΈ μ€μ κ΅¬μ± μμκ° λ λ² μ΄μ λ λλ§λμ΄μΌ νλ κ²½μ° λ λ²μ§Έ λ λλ§μμ λͺ¨μ ββλΌμ°ν°λ₯Ό μ¬μ©νμ§ μλλ€λ κ²μ μκ² λ κ²μ
λλ€. κ·Έλ¦¬κ³ λΉμ μ ν
μ€νΈλ μ€ν¨ν κ²μ
λλ€. mockImplementationOnce()
λ₯Ό μ¬μ©ν΄μΌ νλ νΉλ³ν μ΄μ κ° μλ ν νμ mockImplementation()
λ₯Ό μ¬μ©νλ κ²μ΄ κ°μ₯ μ’μ΅λλ€.
ν
μ€νΈλ³λ‘ κ³ μ ν 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
ν¨μλ‘ λΆν ν μ μμ΅λλ€.
λν BIG :+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
μ clearMocks μμ±μ μΆκ°νμμμ€.
μ°Έκ³ λ‘ νΉμ λ΄λ³΄λ΄κΈ°λ₯Ό λμμΌλ‘ νλ €λ κ²½μ° 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
}
}
λν withRouter
λλ useRouter
λ₯Ό νμ©νλ κ΅¬μ± μμλ₯Ό mount
ν΄μΌ νκ³ μ΄λ₯Ό μ‘°λ‘±νκ³ μΆμ§λ μμ§λ§ μ¬μ ν κ·Έμ λν/μ£Όλ³ ν
μ€νΈλ₯Ό μμ±νλ €λ κ²½μ° κ·Έλ° λ€μ ν
μ€νΈλ₯Ό μν΄ μ΄ 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,
};
@flybayer κ°μ¬ν©λλ€! μ μλν©λλ€!
@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
μ clearMocks μμ±μ μΆκ°νμμμ€.μ°Έκ³ λ‘ νΉμ λ΄λ³΄λ΄κΈ°λ₯Ό λμμΌλ‘ νλ €λ κ²½μ°
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 } }
λν
withRouter
λλuseRouter
λ₯Ό νμ©νλ κ΅¬μ± μμλ₯Όmount
ν΄μΌ νκ³ μ΄λ₯Ό μ‘°λ‘±νκ³ μΆμ§λ μμ§λ§ μ¬μ ν κ·Έμ λν/μ£Όλ³ ν μ€νΈλ₯Ό μμ±νλ €λ κ²½μ° κ·Έλ° λ€μ ν μ€νΈλ₯Ό μν΄ μ΄ 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: require.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
λ΄λ³΄λ΄κΈ°λ§ νμνλ―λ‘ μ΄κ²μ΄ λ΄ λͺ©μ μ μΆ©λΆν μ μλνμ΅λλ€.