React Jest/Enzyme Testing: useHistory Hook Breaks Test - reactjs

I'm fairly new to React, so please forgive my ignorance. I have a component:
const Login: FunctionComponent = () => {
const history = useHistory();
//extra logic that probably not necessary at the moment
return (
<div>
<form action="">
...form stuff
</form>
</div>
)
}
When attempting to write jest/enzyme test, the one test case I've written is failing with the following error
` › encountered a declaration exception
TypeError: Cannot read property 'history' of undefined`
I've tried to use jest to mock useHistory like so:
jest.mock('react-router-dom', () => ({
useHistory: () => ({ push: jest.fn() })
}));
but this does nothing :( and I get the same error. Any help would be most appreciated
UPDATE:
So I figured it out. I was on the right path creating a mock for the useHistory() hook, I defined in the wrong place. Turns the mock needs to be define (at least for useHistory) outside of the scope of the test methods, ex:
import { shallow } from 'enzyme';
import React from 'react';
import Login from './app/componets/login.component';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useHistory: () => ({ push: jest.fn() })
}));
/**
* Test suite describing Login test
describe('<LoginPage>', () => {
test('should test something', () => {
//expect things to happen
});
})
With the above test runs without failing on history being undefined.

So I figured it out. I was on the right path by creating a mock for the useHistory() hook, I just defined it in the wrong place. Turns out the mock needs to be define (at least for useHistory) outside of the scope of the test methods, ex:
import { shallow } from 'enzyme';
import React from 'react';
import Login from './app/componets/login.component';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useHistory: () => ({ push: jest.fn() })
}));
/**
* Test suite describing Login test
*/
describe('<LoginPage>', () => {
test('should test something', () => {
//expect things to happen
});
})
With the above, the test runs without failing on history being undefined.

Related

Mocking function in typescript using RTL to test component props

import {
render,
} from '#testing-library/react'
import React from 'react'
import '#testing-library/jest-dom'
import SimpleDialog from './SimpleDialog'
describe('Testing a simple Dialog component', () => {
it('has the open props set to true', () => {
const MockComponent = jest.fn(() => { return <SimpleDialog open></SimpleDialog> })
render(<MockComponent />)
expect(Dialog).toHaveBeenCalledWith(expect.objectContaining({ open : true }), expect.anything())
})
})
Matcher error: received value must be a mock or spy function
I am trying to get the test pass, but it doesn't seem to work although on a different question it is implied this should be working. Any workaround?
I tried returning a simple function, and it didn't work either.

Render same component in beforeAll/beforeEvery: testing-library/react

I'm testing different things in a single component in separate tests. I want to not have to write render inside every single test, but the code underneath does not work.
I have understood that the cleanup function clears the rendered component after each test, so that is good.
import React from "react";
import { Router } from "react-router-dom";
import { render } from "#testing-library/react";
import "#testing-library/jest-dom";
import myComp from './myComp'
const renderComponent = () => {
return render(<myComp />);
};
describe("desc", () => {
beforeEach(() => {
const {getAllByText, getByText, getByRole} = renderComponent()
});
test("1", () => {
console.log(getAllByText) // not defined
});
test("2", () => {
console.log(getAllByText) // not defined
});
})
The setup above results in the error:
ReferenceError: getAllByText is not defined
My current workaround is to include renderComponent() function call in each test, but this does not look so clean.
test("1", () => {
const {getAllByText, getByText, getByRole} = renderComponent()
});
Attempt:
let result;
beforeEach(() => {
result = renderComponent();
}
test("renders success state", () => {
const { getByText } = result;
expect(getByText(noAccess)).toBeInTheDocument();
expect(getByText(applyForAccessButton)).toBeInTheDocument();});
Error I get then is:
TypeError: Cannot read property 'getByText' of undefined
getAllByText is local to beforeEach function, it's not defined in test scopes where it's accessed. In order to be workable this way, it should be:
let getAllByText, getByText, getByRole;
beforeEach(() => {
({getAllByText, getByText, getByRole} = renderComponent());
});
...
sorry late to the party but i will point out a good practice by kent c dodd
import { render, screen } from '#testing-library/react';
describe('Your Page',() => {
beforeEach(() => {
render(
<YourComponent />
);
})
test("renders success state", () => {
expect(screen.getByText(noAccess)).toBeInTheDocument();
})
})
here is the article refers to using screen.getByText rather than destructing it.
The benefit of using screen is you no longer need to keep the render call destructure up-to-date as you add/remove the queries you need. You only need to type screen. and let your editor's magic autocomplete take care of the rest.
link to the article : https://kentcdodds.com/blog/common-mistakes-with-react-testing-library

React jest enzyme function called test

Hi I have a simple component I need to test:
MyComponent.js-----
import React from 'react';
const MyComponent = (props) => {
onClickHandler = () => {
console.log('clicked');
props.outsideClickHandler();
}
return (
<div>
<span className='some-button' onClick={onClickHandler}></span>
</div>
);
}
MyComponent.test.js----
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
const onClickHandler = jest.fn();
it('calls click event', () => {
const wrapper = shallow(<MyComponent />);
wrapper.find('.some-button').simulate('click');
expect(onClickHandler.mock.calls.length).toEqual(1); // tried this first
expect(onClickHandler).toBeCalled(); // tried this next
});
});
Tried above two types of expect, my console log value is coming
console.log('clicked'); comes
but my test fails and I get this:
expect(received).toEqual(expected) // deep equality
Expected: 1
Received: 0
So, the problem with your code is when you simulate a click event, you expect a totally independent mock function to be called. You need to attach the mock function to the component. The best way is using prototype. Like this:
it('calls click event', () => {
MyComponent.prototype.onClickHandler = onClickHandler; // <-- add this line
const wrapper = shallow(<MyComponent />);
wrapper.find('.some-button').simulate('click');
expect(onClickHandler.mock.calls.length).toEqual(1);
expect(onClickHandler).toBeCalled();
expect(onClickHandler).toHaveBeenCalledTimes(1); // <-- try this as well
});
Refer to this issue for more potential solutions.

Testing a component that uses useEffect using Enzyme shallow and not mount

// MyComponent.jsx
const MyComponent = (props) => {
const { fetchSomeData } = props;
useEffect(()=> {
fetchSomeData();
}, []);
return (
// Some other components here
)
};
// MyComponent.react.test.jsx
...
describe('MyComponent', () => {
test('useEffect', () => {
const props = {
fetchSomeData: jest.fn(),
};
const wrapper = shallow(<MyComponent {...props} />);
// THIS DOES NOT WORK, HOW CAN I FIX IT?
expect(props.fetchSomeData).toHaveBeenCalled();
});
});
When running the tests I get:
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
The expect fails because shallow does not call useEffect. I cannot use mount because of other issues, need to find a way to make it work using shallow.
useEffect is not supported by Enzyme's shallow rendering. It is on the roadmap (see column 'v16.8+: Hooks') to be fixed for the next version of Enzyme, as mentioned by ljharb
What you're asking is not possible with the current setup. However, a lot of people are struggling with this.
I've solved / worked around this by:
not using shallow rendering from Enzyme anymore
use the React Testing Library instead of Enzyme
mocking out modules via Jest
Here's a summary on how to mock modules, based on Mock Modules from the React docs.
contact.js
import React from "react";
import Map from "./map";
function Contact(props) {
return (
<div>
<p>
Contact us via foo#bar.com
</p>
<Map center={props.center} />
</div>
);
}
contact.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Contact from "./contact";
import MockedMap from "./map";
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<p>A dummy map.</p>
);
};
});
it("should render contact information", () => {
const center = { lat: 0, long: 0 };
act(() => {
render(
<Contact
name="Joni Baez"
email="test#example.com"
site="http://test.com"
center={center}
/>,
container
);
});
});
Useful resources:
React Testing Library docs - mocking
React docs - Mocking Modules
Here's a solution from a colleague of mine at CarbonFive:
https://blog.carbonfive.com/2019/08/05/shallow-testing-hooks-with-enzyme/
TL;DR: jest.spyOn(React, 'useEffect').mockImplementation(f => f())
shallow doesn't run effect hooks in React by default (it works in mount though) but you could use jest-react-hooks-shallow to enable the useEffect and useLayoutEffect hooks while shallow mounting in enzyme.
Then testing is pretty straightforward and even your test specs will pass.
Here is a link to a article where testing the use-effect hook has been clearly tackled with shallow mounting in enzyme
https://medium.com/geekculture/testing-useeffect-and-redux-hooks-using-enzyme-4539ae3cb545
So basically with jest-react-hooks-shallow for a component like
const ComponentWithHooks = () => {
const [text, setText] = useState<>();
const [buttonClicked, setButtonClicked] = useState<boolean>(false);
useEffect(() => setText(
`Button clicked: ${buttonClicked.toString()}`),
[buttonClicked]
);
return (
<div>
<div>{text}</div>
<button onClick={() => setButtonClicked(true)}>Click me</button>
</div>
);
};
you'd write tests like
test('Renders default message and updates it on clicking a button', () => {
const component = shallow(<App />);
expect(component.text()).toContain('Button clicked: false');
component.find('button').simulate('click');
expect(component.text()).toContain('Button clicked: true');
});
I'm following this advice and using mount() instead of shallow(). Obviously, that comes with a performance penalty, so mocking of children is advised.

Props aren't passing inside component in test cases written with Jest and Enzyme

This is my test case
import React from 'react';
import { mount } from 'enzyme';
import CustomForm from '../index';
describe('Custom Form mount testing', () => {
let props;
let mountedCustomForm;
beforeEach(() => {
props = {
nav_module_id: 'null',
};
mountedCustomForm = undefined;
});
const customform = () => {
if (!mountedCustomForm) {
mountedCustomForm = mount(
<CustomForm {...props} />
);
}
return mountedCustomForm;
};
it('always renders a div', () => {
const divs = customform().find('div');
console.log(divs);
});
});
Whenever I run the test case using yarn test. It gives the following error TypeError: Cannot read property 'nav_module_id' of undefined.
I have already placed console at multiple places in order to see the value of props. It is getting set. But it couldn't just pass through the components and give the error mentioned above.
Any help would be appreciated been stuck for almost 2-3 days now.
You have to wrap the component that you want to test in beforeEach method such that it becomes available for all the 'it' blocks, and also you have to take the mocked props that you think you are getting into the original component.
import React from 'react'
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import {Map} from 'immutable'
import {ClusterToggle} from '../../../src/MapView/components/ClusterToggle'
describe('component tests for ClusterToggle', () => {
let dummydata
let wrapper
let props
beforeEach(() => {
dummydata = {
showClusters: true,
toggleClustering: () => {}
}
wrapper = shallow(<ClusterToggle {...dummydata} />)
props = wrapper.props()
})
describe(`ClusterToggle component tests`, () => {
it(`1. makes sure that component exists`, () => {
expect(wrapper).to.exist
})
it('2. makes sure that cluster toggle comp has input and label', () => {
expect(wrapper.find('input').length).to.eql(1)
expect(wrapper.find('label').length).to.eql(1)
})
it('3. simulating onChange for the input element', () => {
const spy = sinon.spy()
const hct = sinon.spy()
hct(wrapper.props(), 'toggleClustering')
spy(wrapper.instance(), 'handleClusterToggle')
wrapper.find('input').simulate('change')
expect(spy.calledOnce).to.eql(true)
expect(hct.calledOnce).to.eql(true)
})
})
})

Resources