1
votes

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.

1

1 Answers

1
votes

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: {}
    }),
}