Test react components with jest and enzyme and css modules - reactjs

I have a project which was created using create react app.
Lets say I have a simple component like this -
import React from 'react';
import React, { useState } from "react";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = () => {
setData(data + 1);
};
return (
<div className="button" onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
I will test the component like this -
import React from "react";
import { shallow } from "enzyme";
import Example from "./Example";
it("example test", () => {
const wrraper = shallow(<Example />);
wrraper.find(".button").simulate("click");
expect("test somethig");
});
If I will use styles.module like this -
import React, { useState } from "react";
import styles from "./styles.module.scss";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = () => {
setData(data + 1);
};
return (
<div className={styles.button} onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
I will not be able to find and element in the test using ".button" anymore, since webpack will add a hash to my class name when I am using css modules.
So how can I test react compoennt while using css modules? Only by adding an Id to the element?
It fills wrong to change my code so I will be able to test it.

There are many alternative selectors, which you can find here. That said, when working with (s)css modules, I tend to lean on the element position within the component:
wrapper.find("div").first() will select the first div element within the component heirarchy (or in your example, it'll select the div with the "styles.button" className).
Another alternative is to use template literals. With the example below, I essentially created an escape hatch to select .some-classname:
import React, { useCallback, useState } from "react";
import { button } from "./styles.module.scss";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = useCallback(() => {
setData(prevSate => prevState + 1);
}, [setData]);
return (
<div className={`${button} some-classname`} onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
And lastly, you can use a data-attribute -- like data-test-id (which is becoming more popular because of react-testing-library) to create a simple static selector that can be removed with an additional babel plugin for production builds).

I'm kinda late to the party but I was wondering this myself. You just have to import your scss module into your test file, like this :
import React from "react";
import { shallow } from "enzyme";
import Example from "./Example";
import styles from "./styles.module.scss
it("example test", () => {
const wrraper = shallow(<Example />);
wraper.find(`.${styles.button}`).simulate("click");
expect("test somethig");
});

Related

React add components dynamically from dynamically added component

What I am trying to do is to add components iteratively from the last dynamically added component.
|dynamically added component| ----on click----> |added new component| ---on click--> | added another component|
import React, { useState } from 'react';
import Product from '../../Product';
import Project from '../../Project';
const NewProject = () => {
const [productList, setProductList] = useState([]);
const addProduct = () => {
setProductList([...productList, <Product addProduct={addProduct} key={productList.length}></Product>]);
};
return <React.Fragment>
<Project addProduct={addProduct}>
</Project>
{productList}
</React.Fragment>
}
export default NewProject;
Basically, I'm passing addProduct const to the Product component and it executes the addProduct function again. I see addProduct is triggered but I don't see component came. I think I'm mixing some states there.
Adding Product component from Project component props works fine by the way. But I want to do the same with added product components.
I don't know the exact problem but the way I use it is a little bit different instead of saving Product Component to the list, I only save the product data then I create a another function to render them into components and then pass it in return like below:
import React, { useState } from 'react';
import Product from '../../Product';
import Project from '../../Project';
const NewProject = () => {
const [products, setProducts] = useState([{}]);
const addProduct = () => {
setProducts([...products, {}]);
};
const renderProducts = () => products.map((_, i) => <Product addProduct={addProduct}/>)
return <>{renderProducts()}</>;
};
export default NewProject;

Testing with jest on component with custom hook

i'm trying to write test code for my component that uses a custom hook to seperate logic from view
The problem is that i cannot for whatever reason seem to actually mock this custom hook in a test.
Following is a code example of what im trying to do:
// click-a-button.tsx
import {useClickAButton} from "./hooks/index";
export const ClickAButton = () => {
const { handleClick, total } = useClickAButton();
return <button onClick={handleClick}>{total}</button>;
}
// hooks/use-click-a-button.tsx
import React, {useCallback, useState} from 'react';
export const useClickAButton = () => {
const [total, setTotal] = useState<number>(0);
const handleClick = useCallback(() => {
setTotal(total => total + 1);
}, []);
return {
handleClick,
total,
};
}
// click-a-button.test.tsx
import * as React from 'react';
import {act} from "react-dom/test-utils";
import {render} from "#testing-library/react";
import {useClickAButton} from './hooks/index'
import {ClickAButton} from "./index";
const hooks = { useClickAButton }
test('it runs with a mocked customHook',() => {
const STATE_SPY = jest.spyOn(hooks, 'useClickAButton');
const CLICK_HANDLER = jest.fn();
STATE_SPY.mockReturnValue({
handleClick: CLICK_HANDLER,
total: 5,
});
const component = render(<ClickAButton />);
expect(component.container).toHaveTextContent('5');
act(() => {
component.container.click();
});
expect(CLICK_HANDLER).toHaveBeenCalled();
})
When running the test, neither of the expects is fulfilled.
Context gets to be 0 instead of the mocked 5 and the CLICK_HANDLER is never called.
All in all it seems that the jest.spyon has no effect.
Please help
it seems i found the answer myself.
// right after imports in test file
jest.mock('./hooks')
is all that it took!

React.js - passing functions between components using Context API - not working

I am trying to pass a function between two components but even though I do not have any errors, the function that I am passing wont show or to be precise it is not working. I have two files and one of them is creating a context while the other is using it (obviously). Now, they are not shown in App.js (which is rendered in index.js, usual stuff) they are in the seperate folder. I am using React Router to show one the pages (News.js).
Here are the files:
NewsContext.js
import React, { useContext, createContext, useState, useEffect } from "react";
export const newsK = React.createContext();
export const NewsContext = (props) => {
const working = () => {
console.log("it is working");
};
return <newsK.Provider value={working}>{props.children}</newsK.Provider>;
};
export default NewsContext;
News.js
import React, { useContext, useState, useEffect } from "react";
import { newsK } from "./NewsContext";
import { NewsContext } from "./NewsContext";
const News = () => {
const data = useContext(newsK);
return (
<NewsContext>
<div>
<button onClick={data}></button>
</div>
</NewsContext>
);
};
export default News;
When i pressed the button, it wont do anything. Any tips?
You're trying to use context outside the NewsContext component
The solution for this will be to create a component that will call useContext inside NewsContext.
I.e.
const WrappedButton = () => {
const data = useContext(newsK)
return <button onClick={data} />
}
And then put it inside the NewsContext:
<NewsContext>
<div>
<WrappedButton />
</div>
</NewsContext>

React Maximum update depth exceeded error caused by useEffect only in test

I am trying to write some tests to my react component using jest and react-testing-library.
My component looks like:
//DocumentTable.js
import {useTranslation} from "react-i18next";
import ReactTable from "react-table-6";
import {connect} from "react-redux";
...
export const DocumentTable = ({documents, getDocuments, ...}) => {
const {t} = useTranslation();
const [data, setData] = useState([])
useEffect(() => {
getDocuments();
}, [])
useEffect(() => {
setData(() => translate(documents.map(doc => Object.assign({}, doc))))
}, [documents, t])
const translate = (tempDocuments) => {
if (tempDocuments[0]) {
if (tempDocuments[0].name) {
tempDocuments.forEach(doc => doc.name = t(doc.name));
}
if (tempDocuments[0].documentStatus) {
tempDocuments.forEach(doc => doc.documentStatus = t(doc.documentStatus));
}
}
return tempDocuments;
}
...
return (
<div className="col m-0 p-0 hidden-overflow-y">
<ReactTable
className="bg-dark dark-table"
data={data}
...
)
}
...
export default connect(mapStateToProps, matchDispatchToProps)(DocumentTable);
As you can see I am using redux and translation from react-i18next.
I am using this component to show values received from prop documents in ReactTable component from react-table-v6. To avoid editing my original value I create deep copy of documents array, translate it and put it into data which is used directly in my table.
I have started write my test from check if I can render my component properly using react-testing-library:
//DocumentTable.test.js
import React from 'react'
import {render} from '#testing-library/react'
import {DocumentTable} from "../../../components/content/DocumentTable";
import {I18nextProvider} from "react-i18next";
import i18n from "../../../i18n";
it("Should render component", () => {
const documents = [
{
name: "favourite",
documentStatus: "new"
},
{
name: "simple",
documentStatus: "edited"
}
]
render(
<I18nextProvider i18n={i18n}>
<DocumentTable documents={documents} getDocuments={jest.fn()}/>
</I18nextProvider>
);
})
and everything seems to work fine. However I want to use mock of useTranslation hook as I did in my other components tests.
My mock is:
//_mocks_/react-18next.js
module.exports = {
useTranslation: () => ({
t: key => key,
i18n: {}
}),
}
To use it I have added property to jest config:
//package.json
"jest": {
"moduleNameMapper": {
"react-i18next": "<rootDir>/src/tests/_mocks_/react-i18next.js"
}
},
and I have simplified my test:
//DocumentTable.test.js
import React from 'react'
import {render} from '#testing-library/react'
import {DocumentTable} from "../../../components/content/DocumentTable";
it("Should render component", () => {
const documents = [
{
name: "favourite",
documentStatus: "new"
},
{
name: "simple",
documentStatus: "edited"
}
]
render(
<DocumentTable documents={documents} getDocuments={jest.fn()}/>
);
})
and now when I run my test I get following error:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
in DocumentTable (at DocumentTable.test.js:89)
And I dont understand whats going on. I have come to conclusion that problem is caused by my useEffect hook in DocumentTable.js file. When I don't create copy of my props but translate it directly:
useEffect(() => {
setData(() => translate(documents))
}, [documents, t])
everything again works fine. But I must stay with creating copy of it(when user change language I want to translate again original documents).
How can I deal with that?
Thanks in advance.
The problem is that your mock will return a new function t each time, which will trigger the useEffect in you component since t is a dependency.
Use
//_mocks_/react-18next.js
const t = key => key;
module.exports = {
useTranslation: () => ({
t,
i18n: {}
}),
}

SetState outside body reactjs

I try to set state , but I got error "Hooks can only be called inside of the body of a function component."
This is the code import React, { useState } from "react";
import AuthService from "../Services/AuthService";
const [Searchfile, setSearchfile] = useState([]);
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
// const Searchfile = [
// { title: "ayam", singer: "gigi" },
// { title: "ucuk", singer: "asd" },
// ];
export default Searchfile;
Can someone explain to me why that is error and how to fix it ? :D
try adding a functional component and paste the code there.
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p> {count}</p>
<button onClick={() => setCount(count + 1)}>
click here
</button>
</div>
);
}
You are breaking the rules of hooks by attempting to call the useState hook outside of a functional component. After looking over your code, it seems like you want to create a custom hook of some sort so my suggestion would be todo something like this:
import { useState } from 'react'
import AuthService from "../Services/AuthService";
const UseSearchFile = () => {
const [Searchfile, setSearchfile] = useState([]);
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
return {Searchfile}
}
export default UseSearchFile
you would then import it where you need it like so:
import UseSearchFile from ...correct path
and call it like so:
const { Searchfile } = UseSearchFile()

Resources