How to test a function that changes the state of my component? - javascript

I have this component:
// imports
class FiltersModal extends React.Component {
state = {
status: '',
carrier: '',
};
applyFilters = () => {
const { applyFilters } = this.props;
const {
status,
carrier,
} = this.state;
applyFilters({
status,
carrier,
});
};
handleChange = field => ev => {
this.setState({ [field]: ev.target.value });
};
render() {
const { t, isFiltersModalOpened, toggleFiltersModal } = this.props;
const { shippedDate } = this.state;
return (
<Modal
open={isFiltersModalOpened}
onRequestClose={toggleFiltersModal}
onRequestSubmit={this.applyFilters}
>
<Form>
<StatusesSelect handleStatus={this.handleChange('status')} />
<GetAllCouriers handleCouriers={this.handleChange('carrier')} />
</Form>
</Modal>
);
}
}
FiltersModal.propTypes = {
t: PropTypes.func.isRequired,
isFiltersModalOpened: PropTypes.bool.isRequired,
toggleFiltersModal: PropTypes.func.isRequired,
applyFilters: PropTypes.func.isRequired,
};
export default translate()(FiltersModal);
And this test:
import React from 'react';
import { shallow } from 'enzyme';
import FiltersModal from '../../FiltersModal';
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate HoC receive the t function as a prop
translate: () => Component => {
Component.defaultProps = { ...Component.defaultProps, t: key => key }; // eslint-disable-line
return Component;
},
}));
describe('FiltersModal component test', () => {
let props;
beforeEach(() => {
props = {
t: k => k,
isFiltersModalOpened: false,
toggleFiltersModal: jest.fn(() => k => k),
removeFilter: jest.fn(() => k => k),
applyFilters: jest.fn(() => k => k),
softlayerAccountId: '232279',
filters: {
carrier: 'UPS',
shipmentId: '1234',
shipmentType: '',
shippedDate: '',
shippedFrom: '',
shippedTo: '',
status: '',
},
};
});
it('should render without errors', () => {
const wrapper = shallow(<FiltersModal {...props} />);
expect(wrapper.find('Modal')).toHaveLength(1);
expect(wrapper.find('Form')).toHaveLength(1);
});
it('should change state', () => {
const wrapper = shallow(<FiltersModal {...props} />);
wrapper.setState({ carrier: 'UPS' });
wrapper.instance().applyFilters();
wrapper.instance().handleChange('status');
expect(props.applyFilters).toHaveBeenCalledTimes(1);
expect(wrapper.instance().handleChange).toHaveBeenCalledTimes(1);
});
});
What I need is to call the function handleChange but I am getting this error:
FAIL src/client/pages/Shipments/__tests__/components/FiltersModal-test.js
FiltersModal component test
✓ should render without errors (15ms)
✕ should change state (12ms)
● FiltersModal component test › should change state
expect(jest.fn())[.not].toHaveBeenCalledTimes()
jest.fn() value must be a mock function or spy.
Received:
function: [Function anonymous]
51 |
52 | expect(props.applyFilters).toHaveBeenCalledTimes(1);
> 53 | expect(wrapper.instance().handleChange).toHaveBeenCalledTimes(1);
| ^
54 | });
55 | });
56 |
What am I missing?

I believe you don't need that. As well as in the real project you typically don't communicate with component's method from the outside(ref to call instance's methods is an exception). Instead you rely on what render() returns.
So I propose you to ensure that changed values go outsides through applyFilters:
it('should change state', () => {
const applyFiltersMock = jest.fn();
const wrapper = shallow(<FiltersModal {...props} applyFilters={applyFiltersMock} />);
wrapper.find(StatusesSelect).props().handleStatus({ target: {value: '2'} });
wrapper.find(GetAllCouriers ).props().handleCouriers({ target: {value: '3'} });
wrapper.find(Modal).props().onRequestSubmit();
expect(applyFiltersMock).toHaveBeenCalledTimes(1);
expect(applyFiltersMock).toHaveBeenCalledWith({status: '2', carrier: '3'});
});

handleChange is an actual function, not a mock or spy (as indicated by the error).
If you don't want to mock the function, you can use a spy to check if it has been called:
const spy = jest.spyOn(wrapper.instance(), "handleChange");
wrapper.instance().handleChange("status");
expect(spy).toHaveBeenCalledTimes(1);

You can check state of component by using state. After triggering handleChange check the state of component, if state is changed to desired it should pass the test

Related

Warning: An update to FormItem inside a test was not wrapped in act(...) react testing

I'm getting warnings while adding tests for my react components so how can I get rid of these warnings.
warnings
Warning: An update to FormItem inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. > Learn more at https://reactjs.org/link/wrap-tests-with-act
at FormItem (/home/yousharizvi/Workspace/zain/app/app-> test/node_modules/antd/lib/form/FormItem.js:103:20)
at form
at Form (/home/yousharizvi/Workspace/zain/app/app-test/node_modules/rc-> field-form/lib/Form.js:33:19)
at SizeContextProvider (/home/yousharizvi/Workspace/zain/app/app- test/node_modules/antd/lib/config-provider/SizeContext.js:19:23)
at InternalForm (/home/yousharizvi/Workspace/zain/app/app-test/node_modules/antd/lib/form/Form.js:66:27)
at Search (/home/yousharizvi/Workspace/zain/app/app-test/src/components/Search.tsx:10:36)
at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27381:9)
at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25292:5)
at dispatchSetState (node_modules/react-dom/cjs/react-dom.development.js:17342:16)
at safeSetState (node_modules/rc-util/lib/hooks/useState.js:41:5)
at onMetaChange (node_modules/antd/lib/form/FormItem.js:165:5)
at Field._this.triggerMetaEvent (node_modules/rc-field-form/lib/Field.js:146:67)
test file
import { render, fireEvent } from '#testing-library/react';
import { Search } from '.';
describe('Search Component', () => {
test('test fields are rendered', () => {
const props = {
onSubmit: jest.fn(),
loading: false
}
const { container, getByText } = render(<Search {...props} />)
const searchField = container.querySelector('#searchText');
const button = getByText("Submit");
expect(searchField).toBeTruthy();
expect(button).toBeTruthy();
});
test('test search field by entering text', () => {
const props = {
onSubmit: jest.fn(),
loading: false
}
const { container } = render(<Search {...props} />)
const inputs = container.querySelectorAll('#searchText')
const searchField = inputs[0];
fireEvent.change(searchField, { target: { value: "foo" } });
expect(searchField).toHaveValue('foo');
});
test('test search field should throw error', async () => {
const props = {
onSubmit: jest.fn(),
loading: false
}
const { container, getByText } = render(<Search {...props} />)
const button = getByText("Submit");
fireEvent.click(button);
await new Promise((r) => setTimeout(r, 100)); // waiting for error
expect(container.getElementsByClassName('ant-input-status-error').length).toBe(1);
});
test('test search field should not throw error', async () => {
const props = {
onSubmit: jest.fn(),
loading: false
}
const { container, getByText } = render(<Search {...props} />)
const inputs = container.querySelectorAll('#searchText')
const searchField = inputs[0];
const button = getByText("Submit");
fireEvent.change(searchField, { target: { value: "foo" } });
expect(searchField).toHaveValue('foo');
fireEvent.click(button);
expect(container.getElementsByClassName('ant-input-status-error').length).toBe(0);
});
})
setupTests.ts
import '#testing-library/jest-dom';
global.matchMedia = global.matchMedia || function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};

Jest:- TypeError: Cannot read properties of undefined (reading 'params'). Error coming in jest

I have below component in react. I put in short only
export interface EditCertificateProps {
id:string;
}
export function EditCertificate(props: any) {
injectStyle();
const {id} = props.match.params;
const history = useHistory();
}
When I am doing jest testing it is throwing error.
const id = '123';
describe('EditCertificate', () => {
const params = {
id: '123',
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate id={id} />);
expect(baseElement).toBeTruthy();
});
});
it is throwing error
from another component this page gets called like below.
<SecureRoute path="/:id/edit" component={EditCertificate} />
I changed my testcase like below still error.
describe('EditCertificate', () => {
const props = {
match: {
params: 123,
},
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate {...props.match.params} />);
expect(baseElement).toBeTruthy();
});
});
what wrong I am doing?
The EditCertificate component is expecting a match prop with a params property.
export function EditCertificate(props: any) {
injectStyle();
const {id} = props.match.params;
const history = useHistory();
...
}
This match prop needs to be provided in the unit test. You are creating a props object so you can just spread that into EditCertificate. Spread the entire props object, not props.match.params, the latter only spreads the individual params.
describe('EditCertificate', () => {
const props = {
match: {
params: {
id: 123, // <-- props.match.params.id
},
},
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate {...props} />);
expect(baseElement).toBeTruthy();
});
});
The next issue will be a missing routing context for the useHistory hook. You can provide a wrapper for the render util, or simply wrap EditCertificate directly.
const RouterWrapper = ({ children }) => (
<MemoryRouter>{children}</MemoryRouter> // *
);
...
const { baseElement } = render(
<EditCertificate {...props} />,
{
wrapper: RouterWrapper
},
);
or
const { baseElement } = render(
<MemoryRouter>
<EditCertificate {...props} />
</MemoryRouter>
);
* MemoryRouter used for unit testing since there is no DOM

Not able to mock a constructor´s property: ReferenceError: EventSource is not defined (Jest, Enzyme)

In my test file I am mounting a Component and one of the nested Components is making me troubles. This is the Component:
class CacheHandler extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: () => {
if (caches) {
caches.keys().then((names) => {
names.forEach((name) => {
caches.delete(name);
})
});
}
window.location.reload(true);
}
};
// ...some other code
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
}
}
CacheHandler.propTypes = {
children: PropTypes.func.isRequired
};
export default CacheHandler;
I do not know how properly mock the constructor's refreshCacheAndReload property that gives me grey hair. It would be totally ok if it just does not do anything in the mock, but it shoud be found during the mounting process. At the moment, when I run my test, I get because of that part ReferenceError: EventSource is not defined
This is what I tried inside of my test but failed (Error: "CacheHandler" is read-only.):
const fakeCacheHandler = jest.fn(() => ({
constructor(props) {
//super(props);
this.state = {
loading: false,
isLatestVersion: false,
refreshCacheAndReload: () => { }
}},
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
}
}))
CacheHandler = fakeCacheHandler;
I also tried to define the property directly in test but without success:
Object.defineProperty(CacheHandler, 'CacheHandler', {
value: jest.fn().mockImplementation(query => ({
loading: false,
isLatestVersion: false,
refreshCacheAndReload: () => {}
}))
})
I also tried to mock the whole module in the test like this:
jest.mock('../../components/utilities/CacheHandler', function() {
return jest.fn().mockImplementation(() => {
return {
refreshCacheAndReload: () => {},
render: () => {this.props.children({ loading:false, isLatestVersion:false, refreshCacheAndReload })},
}})
});
but still not successful.
jest.spyOn fails as well(Cannot spy the refreshCacheAndReload property because it is not a function; undefined given instead)
const fakeHandler = new CacheHandler();
const methodSpy = jest.spyOn(fakeHandler, "refreshCacheAndReload");
methodSpy.mockImplementation(() => {
console.log('test!');
})
This is how the test itself look like now:
it('renders MembersList', async () => {
const Component = withMemory(AdminAppCore, ROUTE_ADMIN_MEMBERS);
const result = mount(
<MockProvider stores={{ memberStore, programStore }}>
<Component />
</MockProvider>
);
console.log(result.debug());
await waitForState(result, state => state.loading === false);
expect(result.find(MembersList).length).toBe(1);
result.unmount();
});
I tried to mock the constructor of child element inside the test like this, but if failed(TypeError: this.props.children is not a function):
it('renders MembersList', async () => {
const Component = withMemory(AdminAppCore, ROUTE_ADMIN_MEMBERS);
const mockrefreshCacheAndReload = jest.fn(() => ({}));
const component = shallow(<CacheHandler/>);
component.setState({refreshCacheAndReload: mockrefreshCacheAndReload});
const result = mount(
<MockProvider stores={{ memberStore, programStore }}>
<Component />
</MockProvider>
);
console.log(result.debug());
await waitForState(result, state => state.loading === false);
expect(result.find(MembersList).length).toBe(1);
result.unmount();
});
So I am mounting the AdminAppCore, and inside of the AdminAppCore is the nested component that causes troubles.
Can anyone please explain me how can I mock the refreshCacheAndReload state inside of the nested Component constructor?
As I cannot post this in the comments, so please try this.
const mockrefreshCacheAndReload = jest.fn(() => ({}));
it('tests the method', () => {
const component = shallow(<CacheHandler/>);
component.setState({refreshCacheAndReload: mockrefreshCacheAndReload})
})

How can I test a debounce function with Jest/Enzyme?

I have this component where test coverage says I need to test lines 24 and 25:
class TableToolbarComp extends Component {
state = {
shipmentId: '',
};
debouncedSetFilters = debounce(() => {
const { applyFilters } = this.props; // LINE 24
applyFilters(this.state); // LINE 25
}, 750);
updateShipmentId = ev => {
this.setState(
{
shipmentId: ev.target.value,
},
this.debouncedSetFilters,
);
};
render() {...}
}
And the test:
beforeEach(() => {
applyFilters: k => k,
});
...
it('should trigger button click', () => {
const wrapper = shallow(<TableToolbarComp {...props} />);
wrapper.instance().debouncedSetFilters(750);
wrapper.instance().updateShipmentId({ target: { shipmentId: '124' } });
wrapper.instance().props.applyFilters({ shipmentId: '124' });
});
And I am not getting any errors, it just says those 2 lines need coverage.
I already attempted to called debouncedSetFilters and applyFilters on the test but it's still returning those 2 lines as uncover.
What am I missing?
Function calls cannot be tested efficiently without spies. It should be:
beforeEach(() => {
applyFilters = jest.fn();
});
In order to test asynchronous time-sensitive function, timer mocks should be applied:
jest.useFakeTimers();
const wrapper = shallow(<TableToolbarComp applyFilters={applyFilters} />);
wrapper.instance().debouncedSetFilters();
wrapper.instance().debouncedSetFilters();
expect(applyFilters).not.toHaveBeenCalled();
jest.advanceTimersByTime(750);
expect(applyFilters).toHaveBeenCalledTimes(1);
Then debouncedSetFilters can be stubbed in updateShipmentId test.

Cannot Persist Store Connected Components - React-Redux Tests

I cannot figure out why this is not working:
-spec.js
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
},
store = fakeStore(state),
container = <HomePageContainer store={store} />;
const homePageContainer = shallow(container),
homePage = homePageContainer.find(HomePage);
expect(homePage.props.company.name).to.equal(state.company.name)
});
const fakeStore = state => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
HomePageContainer.js
import React from 'react';
import { connect } from 'react-redux'
import HomePage from '../../client/components/HomePage';
export const mapStateToProps = state => {
company: state.company
}
export { HomePage }
export default connect(mapStateToProps)(HomePage);
HomePage.js
import React, { Component, PropTypes } from 'react';
export default class HomePage extends Component {
render(){
return (
<div className='homepage'>
{/*{this.props.company.name}*/}
</div>
)
}
}
I'm getting this type error because props.company is undefined so for some reason it's not persisting state to :
TypeError: Cannot read property 'name' of undefined
That error is relating to the assert when it's trying to read expect(homePage.props.company.name)
I notice that when putting a breakpoint inside mapStateToProps, that it's not picking up the state object from the store still for some reason:
I know you can pass just the store via props...and if there's nothing in the context, connect() will be able to find it via the store prop. For example in another test suite, this passes just fine:
it('shallow render container and dive into child', () => {
const container = shallow(<ExampleContainer store={fakeStore({})}/>);
expect(container.find(Example).dive().text()).to.equal('Andy');
});
Problem
At this line you pass the store as a prop into <HomePageContainer>:
// ...
container = <HomePageContainer store={store} />;
// ...
But connect() needs the store via context.
And you must wrap the object you want to return in mapStateToProps in parenthese.
Solution
You can use shallow() to make the store available as a context property.
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
};
const store = fakeStore(state);
const homePageContainer = shallow(
<HomePageContainer />,
// Make the store available via context
{ context: { store } }
);
const homePage = homePageContainer.find(HomePage);
expect(homePage.props().company.name).to.equal(state.company.name)
});
Updated mapStateToProps:
export const mapStateToProps = state => ({
company: state.company
});
Found out the problem was really my helper.
This was passing a object with property "state" in it. Not what I want. That would have meant that mapStateToProps would have had to reference the props with state.state.somePropName
const fakeStore = (state) => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
changed it to this, and now mapStateToProps is working fine, it's able to access it with the props as the root level of the object literal, the object I passed in from my test:
const fakeStore = (state) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});

Categories

Resources