I have a Seo component that uses the next/head component. I would like to test this Seo component, but mocking next/head seems to do nothing at all. Am I doing something wrong?
import TestRenderer from 'react-test-renderer';
import Seo from '../Seo';
jest.mock(
"next/head",
() => function Head({ children }) {
return children;
},
);
it('should display seo', () => {
const render = TestRenderer.create(
<Seo />,
);
expect(render.toJSON()).toMatchSnapshot();
});
import Head from "next/head";
function Seo() {
return (
<Head>
<title>Title SEO</title>
</Head>
);
}
This works for me (remove typescript if just using js)
jest.mock('next/head', () => {
return {
__esModule: true,
default: ({ children }: { children: Array<React.ReactElement> }) => {
return <>{children}</>;
},
};
});
I'm not sure if the test should be delayed to wait for the DOM manipulation to end. Also document.head might be needed to be provided as a container.
it('should display seo', async () => {
const render = TestRenderer.create(
<Seo />, {container: document.head}
);
await waitFor(() => {
expect(render.toJSON()).toMatchSnapshot();
});
});
Related
The problem I am having is that the mocked constant does not change in the component after jest.doMock.
Take a look at the minimal repo.
I have tried with mock instead of doMock- same error.
App.test.js
import React from "react"
import App from './App'
import '#testing-library/jest-dom'
import { render } from "#testing-library/react";
describe('testing app.js', () => {
// To reset manually mocked values
beforeEach(() => {
jest.resetModules()
});
test("SET CONSTANT TO 1", () => {
jest.doMock('./myConstants.js', () => ({
CONSTANT: {
NUMBER: 1
}
}))
const { getByText, getByLabelText } = render(<App />)
expect(getByText('1')).toBeInTheDocument()
})
test("SET CONSTANT TO 3", () => {
jest.doMock('./myConstants.js', () => ({
CONSTANT: {
NUMBER: 3
}
}))
const { getByText, getByLabelText } = render(<App />)
expect(getByText('3')).toBeInTheDocument()
})
})
App.js
import React from "react"
import { CONSTANT } from './myConstants.js'
console.log(CONSTANT)
const App = () => {
return (
<div>
{CONSTANT.NUMBER}
</div>
);
}
export default App;
myConstants.js:
export const CONSTANT = { NUMBER: 2 }
Both of the tests above fail. The output from one of them is:
TestingLibraryElementError: Unable to find an element with the text: 3. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div>
2
</div>
</div>
</body>
29 | }))
30 | const { getByText, getByLabelText } = render(<App />)
> 31 | expect(getByText('3')).toBeInTheDocument()
Extended solution
Although the provided solution worked perfectly, I did not want to rewrite each component I was going to test (by adding require(...)). A workaround was to use import("./App").then((module)
import React from "react"
import App from './App'
import '#testing-library/jest-dom'
import { render } from "#testing-library/react";
describe('testing app.js', () => {
// To reset manually mocked values
beforeEach(() => {
jest.resetModules()
});
jest.doMock('./myConstants.js', () => {
return {
__esModule: true,
CONSTANT: {
NUMBER: 1
}
}
})
test("SET CONSTANT TO 1", () => {
// Wait for mock done
return import('./myConstants.js').then((constants) => {
console.log(constants.CONSTANT.NUMBER)
expect(constants.CONSTANT.NUMBER).toBe(1)
import("./App").then((module) => {
const { getByText, getByLabelText } = render(<module.default />)
expect(getByText('1')).toBeInTheDocument()
})
})
})
})
Using jest.doMock is quite complicated than you thought. Basically it requires you do a few steps as described https://jestjs.io/docs/en/jest-object#jestdomockmodulename-factory-options.
Which means you have to change your test to meet above requirements as:
test("SET CONSTANT TO 1", () => {
jest.doMock('./myConstants.js', () => {
return {
__esModule: true,
CONSTANT: {
NUMBER: 1
}
}
})
// Wait for mock done
return import('./myConstants.js').then(() => {
const { getByText, getByLabelText } = render(<App />)
expect(getByText('1')).toBeInTheDocument()
})
})
// While you also require your code right before using instead of `require` at the top level:
const App = () => {
// Require to use it right before using it
const { CONSTANT } = require('./myConstants.js');
return (
<div>
{CONSTANT.NUMBER}
</div>
);
}
I'm guessing we could also mutate the mocked value directly by still using jest.mock as following:
import React from "react"
import App from './App'
import '#testing-library/jest-dom'
import { render } from "#testing-library/react";
import { CONSTANT} from "./myConstants";
jest.mock('./myConstants.js', () => ({
CONSTANT: {
NUMBER: 0,
}
}))
describe('testing app.js', () => {
// To reset manually mocked values
beforeEach(() => {
jest.resetModules()
});
test("SET CONSTANT TO 1", () => {
// Mutate directly mocked value by setting our desired value
CONSTANT.NUMBER = 1;
const { getByText, getByLabelText } = render(<App />)
expect(getByText('1')).toBeInTheDocument()
})
test("SET CONSTANT TO 3", () => {
// Likewise we set it as 3
CONSTANT.NUMBER = 3;
const { getByText, getByLabelText } = render(<App />)
expect(getByText('4')).toBeInTheDocument()
})
})
I know this may be a dumb question.
How can I do to use facebook pixel on a next.js react app ?
there are no dumb questions.
You can see nextjs example about how implements fb pixel.
Nextjs Facebook Pixel
Solution with typescript and hook with NextJs
Install react-facebook-pixel yarn add react-facebook-pixel
In your file _app.tsx
// pages/_app.tsx
import { useEffect } from 'react'
import { useRouter } from 'next/router'
const App = ({ Component, pageProps }) => {
const router = useRouter()
useEffect(() => {
import('react-facebook-pixel')
.then((x) => x.default)
.then((ReactPixel) => {
ReactPixel.init('XXXXXXXXXXXXXXXXXXXXX') // facebookPixelId
ReactPixel.pageView()
router.events.on('routeChangeComplete', () => {
ReactPixel.pageView()
})
})
}, [router.events])
return <Component {...pageProps} />
}
export default App
Remark: it works with typescript or JavaScript
Use the new Script component released in Next.js version 11. Import the below into your _app.js.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import {pageview, FB_PIXEL_ID} from '../../lib/fpixel'
import Script from 'next/script'
const handleRouteChange = () => {
pageview()
}
const FB_PIXEL_ID = process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID
const pageview = () => {
window.fbq('track', 'PageView')
}
const FacebookPixel = ({ children }) => {
const router = useRouter()
useEffect(() => {
// the below will only fire on route changes (not initial load - that is handled in the script below)
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return (
<Script id="facebook-pixel">
{`
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', ${FB_PIXEL_ID});
fbq('track', 'PageView');
`}
</Script>
)
}
export default FacebookPixel
UPDATE
Do not use strategy="lazyOnload". I was previously using this and the script was more likely to be blocked by adblocker if using this method.
There's a library for React called react-facebook-pixel. In order to make it work with NextJs, try this solution in your _app.jsx file:
function FacebookPixel() {
React.useEffect(() => {
import("react-facebook-pixel")
.then((x) => x.default)
.then((ReactPixel) => {
ReactPixel.init('pixel ID here');
ReactPixel.pageView();
Router.events.on("routeChangeComplete", () => {
ReactPixel.pageView();
});
});
});
return null;
}
export default function App({ Component, pageProps }) {
return (
<>
<Head>
<meta charSet="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
</Head>
<FacebookPixel />
//…
<main className="routesContainer">
<Component siteData={siteData} {...pageProps} />
</main>
//…
</>
);
}
or in case you're using Class components, insert this in you componentDidMount() inside the App class:
componentDidMount() {
import('react-facebook-pixel')
.then((x) => x.default)
.then((ReactPixel) => {
ReactPixel.init('Pixel ID Here');
ReactPixel.pageView();
Router.events.on('routeChangeComplete', () => {
ReactPixel.pageView();
});
});
}
font: https://github.com/zsajjad/react-facebook-pixel/issues/53
Simple and easy:
Put the below code inside _app.js:
useEffect(async () => {
const { default: ReactPixel } = await import('react-facebook-pixel');
ReactPixel.init(FB_PIXEL, null, {
autoConfig: true,
debug: true,
});
ReactPixel.pageView();
ReactPixel.track("ViewContent")
});
import React from 'react';
export const HomePage = () => {
const fetchResults = () => {
return new Promise((resolve) => {
Adaptor.fetch();
});
}
const [initialState, dispatch] = useTableState();
if (initialState.fetchNew) {
let promise = fetchResults().then((res) => {
//dispatch
})
throw promise;
}
return <Content />
}
I need to mock the fetchResults method in the homepage component mentioned above in such a way that it should return a resolved promise having mock json result. Tried many approaches but nothing is working out
one of the approach tried was:-
jest.mock("../components/homePage", () => {
return {
fetchResults: () => {
return Promise.resolve({data: [{value: 'test'}]});
},
};
});
Can someone help me to fix this issue.
Thanks in advance.
You are missing to return the default.
Also you can set the mocked data directly.
jest.mock("../components/homePage", () => {
return {
default: () => {
return <Content {...mockedData} />;
},
};
});
I want to test the page get redirected after click on div, but I dont know how, this is my code. Thank you so much
<div className="bellTest">
<NextLink href="/notifications">
<Bell />
</NextLink>
</div>
test.tsx
jest.mock('next/link', () => {
return ({ children }) => {
return children;
};
});
describe('Test of <HeaderContainer>', () => {
test('Test the page redirect after click', async done => {
const wrapper = mount( <HeaderComponent /> );
await wrapper
.find('.bellTest')
.at(0)
.simulate('click');
// expect the page getting redirect
});
});
Instead of mocking next/link you can register a spy on router events, and check if it was called.
The test will look like this:
import Router from 'next/router';
describe('Test of <HeaderContainer>', () => {
const spies: any = {};
beforeEach(() => {
spies.routerChangeStart = jest.fn();
Router.events.on('routeChangeStart', spies.routerChangeStart);
});
afterEach(() => {
Router.events.off('routeChangeStart', spies.routerChangeStart);
});
test('Test the page redirect after click', async done => {
const wrapper = mount(<HeaderComponent />);
await wrapper
.find('.bellTest')
.at(0)
.simulate('click');
expect(spies.routerChangeStart).toHaveBeenCalledWith('expect-url-here');
});
});
I'm trying to write a test for the following:
import React from 'react'
import Popup from 'some-library'
const popupConfig = {
home: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
},
settings: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
}
}
const closePopup = () => {
Popup.closePopup()
}
const toggleNewPopup = () => {
Popup.togglePopup('some-popup')
}
const GetStartedPopup = ({ moduleName }) => {
if (!Object.keys(popupConfig).includes(moduleName)) return null
const {
popupValue = 'Hi there!',
popupStyle = 'warning',
popupMessage = 'Get Started',
popupBtnFunction = toggleNewPopup
} = popupConfig[moduleName]
return (
<Popup
popupValue={popupValue}
popupStyle={popupStyle}
popupBtnValue={popupMessage}
popupBtnStyle="neutral"
popupBtnFunction={popupBtnFunction}
xPopup={closePopup}
/>
)
}
export default GetStartedPopup
The objective of the test is to make sure that the closePopup and toggleNewPopup functions are called. I'm doing the following to do that for the closePopup function:
import React from 'react'
import { mount } from 'enzyme'
import { Popup } from 'some-library'
import GetStartedPopup from 'widgets/getStartedPopup'
describe('<GetStartedPopup/>', () => {
let wrapper
let props
beforeEach(() => {
props = {
page: 'home'
}
wrapper = mount(<GetStartedPopup {...props}/>)
})
it('should render the component without crashing', () => {
expect(wrapper).toBeDefined();
})
it('should call closePopup', () => {
const spy = jest.spyOn(wrapper.instance(), 'closePopup');
wrapper.instance().closePopup();
expect(spy).toHaveBeenCalledTimes(1);
})
afterEach(() => {
wrapper.unmount()
})
})
I went through the docs for spyOn and other SO threads that tackle issues like this but couldn't resolve how to test the closePopup and toggleNewPopup functions for my case here. When I run the test case written above I get this: TypeError: Cannot read property 'closePopup' of null. What would be the correct way to write the test to make sure that the two functions are called?
Funny that I ran into this myself at work in regards to wrapper.instance() doc
To return the props for the entire React component, use wrapper.instance().props. This is valid for stateful or stateless components in React 15.. But, wrapper.instance() will return null for stateless React component in React 16., so wrapper.instance().props will cause an error in this case.
As for the 3rd party library. You should be mocking any collaborators that your component uses.
import { Popup } from 'some-library';
describe('<GetStartedPopup />', () => {
let wrapper;
jest.mock('some-library', () => {
Popup: jest.fn(),
});
const initialProps = {
page: 'home'
};
const getStartedPopup = () => {
return mount(<GetStartedPopup {...initialProps});
};
beforeEach(() => {
Popup.mockClear()
wrapper = getStartedPopup();
};
it('should call closePopup', () => {
expect(Popup.closePopup()).toHaveBeenCalledTimes(1);
});
...
});