How to test componentWillMount inside a connected React-Redux component? - javascript

I have the following smart component which uses componentWillMount lifecycle method to make an async call to fetch data. I am writing tests for it but I am unable to test if the function gets called and if it does it is called before the component gets mounted. They are important cases to cover.
The code for the smart component is as follows:
const mapStateToProps = (state) => { const context = state.context; return {
isError: context.error, }; };
const options = { componentWillMount: (props) => {
const { dispatch } = props;
dispatch(fetchContextUser()); }, };
export function App(props) { return (
<div className="app">
{ props.isError ? (<ContextError />) : (<Main />) }
{ props.children }
</div> ); }
App.propTypes = { children: PropTypes.object, // eslint-disable-line react/forbid-prop-types isError: PropTypes.bool.isRequired, // eslint-disable-line react/forbid-prop-types };
export default connect(mapStateToProps, null)(functional(App, options));
I am using Enzyme, Chai and other mock adapters for testing this component. The test block is follows :
describe.only('Render Connected Component', () => { let store; beforeEach(() => {
store = mockStore({
context: {
error: false,
},
}); }); it('Should render connected components', () => {
const connectedWrapper = mount(
<Provider store={store}>
<ConnectedApp />
</Provider>
);
expect(connectedWrapper).to.be.present(); }); });
I just want to test two things :
1.) fetchContextUser is called
2.) fetchContextUser is called before the component gets mounted

I think #pedro-jiminez is close, but missing two elements here:
In order in invoke componentWillMount in enzyme, you need to use mount, not shallow
That answer assumes that fetchContextUser is a prop, and can be replaced with a spy. In this case, you probably want to stub store.dispatch in your test and assert that it was called with fetchContextUser, assuming that's just a pure function.
So the test would look like:
describe.only('Render Connected Component', () => {
let store;
beforeEach(() => {
store = mockStore({
context: {
error: false,
},
});
// stub dispatch
sinon.stub(store.dispatch);
});
it('Should render connected components', () => {
const connectedWrapper = mount(
<Provider store={store}>
<ConnectedApp />
</Provider>
);
expect(connectedWrapper).to.be.present();
// Now test dispatch called
expect(store.dispatch.calledOnce).to.be.true;
expect(store.dispatch.args[0][0]).to.deepEqual(fetchContextUser());
});
});

I think that you just have to test that the componentWillMount method calls the fetchContextUser action, because you must to trust that ReactJS will call the componentWillMount method before it gets mounted, so, that premise test your second case.
So, to test the first case I think you can do something like this:
import sinon from 'sinon';
...
it('It calls fetchContextUser when calls componentWillMount', () => {
const fetchContextUserSpy = sinon.spy();
const wrapper = shallow(<App fetchContextUser={fetchContextUserSpy} />);
fetchContextUserSpy.reset(); //reset the spy to avoid unwanted calls
wrapper.instance().componentWillMount(); // Call componentWillMount directly
expect(fetchContextUserSpy.calledOnce).to.be.true; // Expect that after call componentWillMount() the fetchContextUserSpy was called once
In this test you just call directly the componentWillMount() function and expects that the fetchContextUserSpy is called, so that test your case 1.
I'm using sinon.js to test when a function is called.
Again your case 2 not needs to be tested because ReactJS guarantees that the componentWillMount method is called before the component is mounted.
=========================================================================
Try this with functional component
it('It calls fetchContextUser when mounted', () => {
const fetchContextUserSpy = sinon.spy();
const wrapper = shallow(<App fetchContextUser={fetchContextUserSpy} />);
expect(fetchContextUserSpy.calledOnce).to.be.true; // Expect that the component calls the fetchContextUserSpy
});

Related

Unable to mock the component with the given set of mocked data using React Testing Library

This is my component and I am trying to mock method fetchChapterData which would return me the mocked data and would set the state of the component with the returned value but it is not rendering the UI with the mocked data and also re-rendering the component multiple times.
import React, { Component } from 'react'
import LoadingImg from '../../assets/images/loader.gif'
import {fetchChapterData} from '../helpers/common'
class MyComponent extends Component {
constructor(props){
super(props)
this.state={
isDataLoaded:false
}
}
render() {
return (
<div className="abc">
{this.state.isDataLoaded?
<div className="abc">
<div className="abc" dangerouslySetInnerHTML={{__html: this.state.data}}></div>
</div>
:
<div className="loading" key="loading_key">
<img src={LoadingImg} alt ="Loading"/>
</div>
}
</div>
)
}
componentDidMount= async()=> {
let result = await fetchChapterData('IntroductionReportChapter')
/*fetchChapterData is being mocked with data {isDataLoaded:true,data:"<p>hello all</p>"}*/
this.setState({...result});
}
}
export default MyComponent;
This is my Test file
so here I am mocking the method fetchChapterData and asking it to return the fake data
import React from 'react'
import MyComponent from './myComponent'
import { render,screen} from "#testing-library/react"
import {fetchChapterData} from '../helpers/common'
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(),
}));
let container = document.createElement("div");
document.body.appendChild(container);
const fakeData={
"isDataLoaded": true,
"data":"<p>hello</p>"
};
describe("Introduction component testing", () => {
it('Introduction section rendered or not',async()=>{
await fetchChapterData.mockResolvedValueOnce({...fakeData})
render(<MyComponent />)
await expect(fetchChapterData).toHaveBeenCalledTimes(1);
})
});
So I got the solution for the same. We need to use the asynchronous version of act to apply resolved promises while rendering the component in the test file, and it would start executing your mocked data.
describe("My component testing", () => {
it("renders user data", async () => {
const fakeData={isDataLoaded:true,intoductionText:" <p>manisha</p>"}
fetchChapterData.mockResolvedValueOnce({...fakeData})
await act(async () => {
render(<MyComponent />, container);
});
screen.getByText('manisha')
});
})
Hey – try mocking your data helper like so
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(() => Promise.resolve({
//Your data here
}))
}))
Also in your state object at the top of the class, you have false1 instead of false do you want to correct that?
You may also want to take another approach to updating the data loaded state property, maybe instead create a helper function in the class that sets that state like so
const handleData = (data) => {
setState({
isDataLoaded: true,
data
})
}
Finally, call this helper in componentDidMount with the result from your fetchChapterData function

How to test a component function being called when componentDidMount?

I have a React JS component MyComponent and I would like to test the following use case:
It should call updateSomething() when component on mount
And I've come up with the following code:
System Under Test (SUT)
export class MyComponent extends React.Component<Props, State> {
public componentDidMount() {
console.log("componentDidMount"); // Debug purpose.
this.fetchSomething()
.then(() => {
console.log("fetchSomething"); // Debug purpose.
this.updateSomething();
});
}
// These are public for simplicity
public async fetchSomething(): Promise<any> { }
public updateSomething() {
console.log("updateSomething"); // Debug purpose.
}
}
Test
it("should update something, when on mount", () => {
const props = { ...baseProps };
sinon.stub(MyComponent.prototype, "fetchSomething").resolves();
const spy = sinon.spy(MyComponent.prototype, "updateSomething");
shallow(<MyComponent {...props} />);
sinon.assert.calledOnce(spy);
});
The result is the test failed with AssertError: expected updateSomething to be called once but was called 0 times but all three console.log() printed.
My understanding is since I want to test the event when on mount, I have to spy/stub it before it's even created, therefore I have to spy on MyComponent.Prototype. Also, for fetchSomething(), I have to stub the async call and make it .resolves() to let it progress.
But I couldn't understand how it can still console.log("updateSomething") without being spied.
I don't know about sinon and I don't know about ts, but with simple js and jest it'd be like this:
fetchSomething() = Promise.resolve();
Then, in your test, you wouldn't have to mock it and just use:
const spy = jest.spyOn(MyComponent.prototype, 'updateSomething');
To see if it was called:
expect(spy).toHaveBeenCalled();
According to the comments/answer from this post, the assertion comes before .updateSomething have been called. To solve this problem, I would've to await the componentDidMount lifecycle method.
So the fixed program is as below:
// SUT
public async componentDidMount() {
//...
return this.fetchSomething()
.then(() => {
//...
});
}
// Test
it("should update something, when on mount", () => {
const props = { ...baseProps };
// Disable lifecycle here to enable stub in between.
const wrapper = shallow(<MyComponent {...props} />, { disableLifecycleMethods: true });
sinon.stub(wrapper.instance(), "fetchSomething").resolves();
const stub = sinon.stub(wrapper.instance(), "updateSomething");
// Actually call component did mount.
wrapper.instance().componentDidMount().then(() => {
sinon.assert.calledOnce(stub);
});
});

Is there a way to test that a function passed with mapDispatchToProps was called using RTL when there is no relevant change in UI?

Is there a way to test that a function passed with mapDispatchToProps was called using RTL when there is no relevant change in UI?
As suggested here, you don't need to test that mapDispatchToPros works (this is redux's responsibility).
What you should probably do is exporting the non-connected component, and passing the jest functions in props directly.
For example :
export const Component = ({ someFn }) => {
...
}
const mapDispatchToProps = {
someFn: ...
};
export connect(...)(Component)
import { Component } from './component.js';
...
it('calls someFn on submit', () => {
const someFn = jest.fn()
const component = mounted(<Component someFn={someFn} />)
component.simulate('submit')
expect(someFn).toHaveBeenCalled()
})
Hope this helps

Jest simulate 'click' works quirky and test fails

I'm trying to test a simple component, that looks like this
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export class App extends PureComponent {
handleClick = (event) => {
const { loadGreetings } = this.props
loadGreetings()
}
render () {
const { hi } = this.props
return (
<section>
<h1 className={styles.earlyDawn}>{hi}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
)
}
}
App.propTypes = {
hi: PropTypes.string,
loadGreetings: PropTypes.func
}
export default App
Here is my test file
import React from 'react'
import {App} from './index'
import {shallow} from 'Enzyme'
describe('Testing App container...', () => {
let props
beforeEach(() => {
props = {
loadGreetings: jest.fn().mockName('loadGreetings'),
hi: 'Hi from test'
}
})
test('should handle click on the button', () => {
const wrapper = shallow(<App {...props}/>)
const buttonHi = wrapper.find('button')
const instance = wrapper.instance()
expect(buttonHi.length).toBe(1)
jest.spyOn(instance, 'handleClick')
buttonHi.simulate('click')
expect(props.loadGreetings).toHaveBeenCalled()
expect(instance.handleClick).toHaveBeenCalled()
})
})
So the problem is in the second toHaveBeenCalled assertion that fails all the time. However, first toHaveBeenCalled seems to be working, which bothers me, because props.loadGreetings is called inside instance.handleClick. Could you please help me to find what may be the problem?
Dependencies: "react": "16.9.0", "react-dom": "16.9.0", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "jest": "^24.8.0",
A simpler approach would be to pass in some initial props and test your component based upon those initial props -- you'll also manipulate those props to add more assertions.
Here's a working example (click on the Tests tab to run the tests):
components/App/index.js
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class App extends PureComponent {
handleClick = () => {
this.props.loadGreetings();
};
render() {
const { message } = this.props;
return (
<section className="app">
<h1>{message}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
);
}
}
App.propTypes = {
message: PropTypes.string,
loadGreetings: PropTypes.func
};
export default App;
components/App/App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./index";
// define the passed in function here for easier testing below
const loadGreetings = jest.fn();
// initial props to pass into 'App'
const initProps = {
message: "hi",
loadGreetings
};
describe("Testing App container...", () => {
let wrapper;
beforeEach(() => {
// this resets the wrapper with initial props defined above
wrapper = shallow(<App {...initProps} />);
});
afterEach(() => {
// this clears any calls to the mocked function
// and thereby resets it
loadGreetings.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find(".app").exists()).toBeTruthy();
expect(loadGreetings).toHaveBeenCalledTimes(0);
});
it("initially renders a 'hi' message and then a 'goodbye' message", () => {
expect(wrapper.find("h1").text()).toEqual("hi");
// manipulating the initial 'message' prop
wrapper.setProps({ message: "goodbye" });
expect(wrapper.find("h1").text()).toEqual("goodbye");
});
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
// since we passed in 'loadGreetings' as a jest function
// we expect it to be called when the 'Handshake' button is
// clicked
wrapper.find("button").simulate("click");
expect(loadGreetings).toHaveBeenCalledTimes(1);
});
});
Not recommended (see below), but you can spy on the class method -- you'll have to work with instances, forceUpdate the instance, then invoke the handleClick method either manually, wrapper.instance().handleClick(), or indirectly via some element's event handler: wrapper.find("button").simulate("click") or wrapper.find("button").props().onClick().
The reason I don't recommend this testing strategy is that you're testing a React implementation (testing whether or not the element's event handler invokes your callback function). Instead, you can avoid that by asserting against whether or not a prop function is called and/or a prop/state change happens. This is a more standard and direct approach to testing the component -- as that is what we care about; we care that the props and/or state changes based upon some user action. In other words, by making assertions against the 'loadGreetings' prop being called we're already testing that the onClick event handler works.
Working example:
App.test.js (same testing as above, with the exception of this test):
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
const spy = jest.spyOn(wrapper.instance(), "handleClick"); // spying on the method class
wrapper.instance().forceUpdate(); // required to ensure the spy is placed on the method
wrapper.find("button").simulate("click");
expect(spy).toHaveBeenCalledTimes(1);
const mockedFn = jest.fn(); // setting the method as a mocked fn()
wrapper.instance().handleClick = mockedFn;
wrapper.instance().forceUpdate(); // required to update the method instance with a mocked fn
wrapper.find("button").simulate("click");
expect(mockedFn).toHaveBeenCalledTimes(1);
});

Using Jest and Enzyme, how do I test a function passed in through props?

Using Jest and Enzyme, how can I test if this.props.functionToTest was run?
class TestComponent extends Component {
static propTypes = {
functionToTest: PropTypes.func
}
componentDidMount() {
this.props.functionToTest()
}
}
In Jest, I've tried creating mockProps and passing them in when mounting the component.
let props = {
functionToTest = jest.fn(() => {});
}
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
A console.log in the componentDidMount function shows functionToTest as undefined. Obviously passing in the props during mount isn't working.
Question 1: How can I pass in mock props that will show in the componentDidMount function?
Question 2: Once that function is available, how do I gain access to the function so I can use spyOn or something similar to test if the function was run?
I don't know your exact setup, but this is how I would do that:
Mock the function with jest.fn() like you did
Pass mock to the component being mounted (like apparently you did)
Check whether it was run with expect(...).toBeCalled() or .toHaveBeenCalled() (varies between different Jest versions)
.
let props = {
functionToTest: jest.fn() // You don't need to define the implementation if it's empty
};
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
// In the test code:
it('does something', () => {
expect(props.functionToTest).toBeCalled();
// OR... depending on your version of Jest
expect(props.functionToTest).toHaveBeenCalled();
});
The problem ended up being that TestComponent was only being exported within the Redux wrapper. Adding an export at the class level and destructuring it in the Jest test import, along with the solution Henrick posted above fixed it.

Categories

Resources