React: How mock functions and test component rendering with jest: - javascript

Im very new to react/jest. Im trying to test a very simple react component that gets data from the server and renders the response. My component looks like the below:
export default class myComponent extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
try {
let response = await axios.get(`server/url/endpoint`);
this._processSuccess(response.data);
} catch(e) {
this._processFail(e);
}
}
_processSuccess(response) {
this.setState({pageTitle: response.data.title, text: response.data.text});
}
render() {
return (
<div className="title">{this.state.pageTitle}</div>
);
}
}
Now I want to test this class. While I test:
I want to make sure componentDidMount() was not called
I want to pass test data to _processSuccess
Finally check the if the rendered output contains a div with class title that has the inner text same as what I supplied as response.data/pageTitle
I tried something like the below:
import React from 'react'
import MyComponent from './MyComponent'
import renderer from 'react-test-renderer'
import { shallow, mount } from 'enzyme'
describe('MyComponent', () => {
it('should display proper title', () => {
const c = shallow(<MyComponent />);
c._processSuccess(
{data:{pageTitle:'siteName', test:'text'}}
);
// couldn't go further as Im getting error from the above line
});
});
But, Im getting MyComponent._processSuccess is not a function error. What would be the proper way to do that.

shallow() returns an Enzyme wrapper with some utils method to test the rendered component. It does not return the component instance. That's why you get the error when calling c._processSucces(). To access the component you can use the .instance() method on the wrapper, so the following should work:
const c = shallow(<MyComponent />);
c.instance()._processSuccess(
{data:{pageTitle:'siteName', test:'text'}}
);
In order to avoid that component's componentDidMount() get called, you can try settings disableLifecycleMethods on the shallow renderer, but I'm not sure about that because here Enzyme's documentation is not 100% clear:
const c = shallow(<MyComponent />, {
disableLifecycleMethods: true
});
Finally, you can check if the output contains the expected <div>, using Enzyme's contains() and one of Jest assertion methods:
expect(c.contains(<div className="title" />)).toBe(true);

Related

getting error Invalid hook call when I call one component in class component

When I used the same method in another project then It was working well but I decided to use the same method for my current project then I am having an issue with the given below
react-dom.development.js:14724 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See some tips for tips about how to debug and fix this problem.
at Object.throwInvalidHookError (react-dom.development.js:14724:13)
at useState (react.development.js:1497:21)
Below is my first component name is GetWindowWidth.js. this component is related to apply the screen with for desktop, tab, and mob screen
GetWindowWidth.js
import {useState,useEffect} from "react";
const GetWindowWidth = () => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
window.addEventListener("resize", updateWidth);
return () => window.removeEventListener("resize", updateWidth);
});
const updateWidth = () => {
setWidth(window.innerWidth);
};
return width;
}
export default GetWindowWidth;
Below is another component where I am trying to call the above component to apply the screen width.
AnotherComponent.js
import React, { Component } from 'react'
import GetWindowWidth from './GetWindowWidth';
export class AnotherComponent extends Component {
render() {
const width = GetWindowWidth();
return (
<div color={width<600? '#161625':"#f3990f"}>UserCatalogTwo</div>
)
}
}
export default AnotherComponent
I don't why this is coming even it's working on other projects.
GetWindowWidth is a hook, not a component, since it doesn't render anything. (And for that reason its name should start with use.) You can't use hooks in class components. You'll have to either rewrite the class component as a function component, or write a non-hook version of GetWindowWidth.
For instance, you might have a module with a function that sets up the resize handler:
// watchwidth.js
export function watchWidth(callback) {
const handler = () => {
callback(window.innerWidth);
};
window.addEventListener("resize", handler);
return () => {
window.removeEventListener("resize", handler);
};
}
...then import it:
import { watchWidth } from "./watchwidth.js";
...and use it in your class component:
componentDidMount() {
this.stopWatchingWidth = watchWidth(width => this.setState({width}));
}
componentWillUnmount() {
this.stopWatchingWidth();
}
That's off-the-cuff and untested, but gives you an idea.

how to throw a react-alert inside of a function in a class based component

Using react-alert module for alerts
My code looks like this -
index.js:
const options = {
// you can also just use 'bottom center'
position: positions.TOP_CENTER,
timeout: 5000,
offset: '30px',
type: types.ERROR,
// you can also just use 'scale'
transition: transitions.FADE
}
ReactDOM.render(<AlertProvider template={AlertTemplate} {...options}>
<App /></AlertProvider>, document.getElementById('root'));
App.js
class App extends React.Component { //then my state, functions, constructors,
//here is the problem
nextClicked = (e) => {
if (//something) {
if (//something) {
const alert = useAlert();
alert.show("ERROR MESSAGE!!!");
}
} // etc
export default withAlert()(App)
Basically, I am getting the error
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
From their docs, it says you can use it with a higher order-component. So, if you
import the withAlert module from react-alert, you can wrap your component when you export it. Again, check the docs on github, this is covered.
Converting the example from the docs to a class component, you get:
import React from 'react'
import { withAlert } from 'react-alert'
class App extends React.component {
constructor(props) {
super(props);
}
render {
return (
<button
onClick={() => {
this.props.alert.show('Oh look, an alert!')
}}
>
Show Alert
</button>
)
}
}
export default withAlert()(App)
Because you wrap the component in the HOC, you get access to the alert prop.

How to test the re-rendering triggered by setState() in componentDidMount() in React 16

I have a component that trigger re-rendering when I call setState() in componentDidMount(). Here is sample code:
import React from 'react'
class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
message: 'Hello'
}
}
componentDidMount () {
this.fetchMessage().then((message) => {
this.setState({ message })
})
}
fetchMessage() {
return Promise.resolve('World')
}
render() {
return this.state.message
}
}
and my test is like this:
import MyComponent from './MyComponent'
import { shallow } from 'enzyme'
describe('<MyComponent>', () => {
it('renders World', () => {
const wrapper = shallow(<MyComponent />)
expect(wrapper.text()).toEqual('World') # => results shows Hello
})
})
Please note, I am using enzyme to assist with my test.
The test failed because the returned results is Hello which is the initial rendering.
So my question here is what's the way to ensure that the re-rendering has triggered before carrying out the assertion?
Hellow,
You have to use mount if you want to test lifeCycleMethod.
Note that setState is async, so if this.fetchMessage take time, you will find yourself into a beautiful race condition on your render and in your test.
To avoid this, try to extract the render into a stateless function.

Render child components with Enzymejs tests

I'm trying to test a simple component that take some props (it have no state, or redux connection) with Enzyme, it works for the plain elements like <div /> and so on, but when i try to test if the element rendered by the child component exists, it fails.
I'm trying to use mount but it spit me a lot of errors, i'm new in this so, here is my code:
import React, { Component } from 'react';
import WordCloud from 'react-d3-cloud';
class PredictWordCloud extends Component {
render() {
const fontSizeMapper = word => Math.log2(word.value) * 3.3;
const { size, data, show } = this.props;
if (!show)
return <h3 className='text-muted text-center'>No data</h3>
return (
<section id='predict-word-cloud'>
<div className='text-center'>
<WordCloud
data={data}
fontSizeMapper={fontSizeMapper}
width={size}
height={300} />
</div>
</section>
)
}
}
export default PredictWordCloud;
It's just a wrapper for <WordCloud />, and it just recieves 3 props directly from his parent: <PredictWordCloud data={wordcloud} size={cloudSize} show={wordcloud ? true : false} />, anything else.
The tests is very very simple for now:
import React from 'react';
import { shallow } from 'enzyme';
import PredictWordCloud from '../../components/PredictWordCloud.component';
import cloudData from '../../helpers/cloudData.json';
describe('<PredictWordCloud />', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PredictWordCloud data={cloudData} size={600} show={true} />)
});
it('Render without problems', () => {
const selector = wrapper.find('#predict-word-cloud');
expect(selector.exists()).toBeTruthy();
});
});
For now it pass but if we change the selector to: const selector = wrapper.find('#predict-word-cloud svg'); where the svg tag is the return of <Wordcloud /> component, the tests fails because the assertion returns false.
I tried to use mount instead of shallow, exactly the same test, but i get a big error fomr react-d3-cloud:
PredictWordCloud Render without problems TypeError: Cannot read property 'getImageData' of null.
This is specially weird because it just happens in the test environment, the UI and all behaviors works perfectly in the browser.
You can find your component directly by Component name.
Then you can use find inside your sub-component as well.
e.g
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.find('svg')).to.have.length(1);
});
or
You can compare generated html structure as well via
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.html()).to.equal('<svg> Just an example </svg>');
});

How to mock an instance created in React JS component?

I'm trying to mock an instance which is created in a React JS component.
The instance is a common ECMAScript 2016 class, not a React component. I use Jasmine, React JS TestUtils and babel-rewire for testing.
My React component code looks like this:
import MyHandler from '../../js/MyHandler';
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myHandler = new MyHandler();
}
someComponentMethod() {
this.myHandler.someMethod();
}
render() {
return <div>...</div>;
}
}
My class looks like this:
export default class MyHandler {
someMethod() {
// ...
}
}
My test and what I tried so far:
// gives exception
let myHandler = new MyHandler();
let spy = spyOn(myHandler, "someMethod").and.returnValue(null);
MyComponent.__Rewire__ ("MyHandler", spy);
// also gives exeption
MyComponent.__set__ ("MyHandler", spy);
let component = TestUtils.renderIntoDocument(<MyComponent />);
For mocking other React components I use babel-rewire which works great. But I can't replace the instance by a mock or spy.
I know I could pass the instace into the component as a property (and thus mock it in a test), but I wonder if this is good practise and I'm afraid I'll have the problem in the next coomponent .
Any help appreciated!
You could use the injector loader something along these lines
const MyComponentInjector = require('inject!'MyComponent');
MyComponent = MyComponentInjector({
'../../js/MyHandler': YourMockedClass
}).default
that would provide a MyComponent with YourMockedClass as Mock for your handler
For those who are interested, here is the complete code:
import TestUtils from 'react-addons-test-utils';
import React from 'react';
import inject from 'inject!../js/MyComponent';
it("mocks the call", () => {
class mockClass {
constructor() {
console.log("some constructor fake");
}
someMethod() {
console.log("some method fake");
}
}
let mock = new mockClass();
let MyComponent = inject({
'../../js/MyHandler': mockClass
}).default;
let component = TestUtils.renderIntoDocument(<MyComponent />);
component.someComponentMethod();
});

Categories