React Enzyme - modify internals of shallow rendered functional component - reactjs

I have a functional react component and I want to change a property value inside the component in an enzyme unit test (in my case the ready attribute). The unit test should use shallow to avoid rendering any children components.
Here is the simplified component code:
import {useTranslation} from 'react-i18next';
// ...
const MyComponent: React.FC<IProps> = (props: IProps) => {
const {t, ready} = useTranslation('my-translation-namespace');
// ...
return (
<React.Fragment>
{ready &&
<div className="my-component">some more content...</div>
}
</React.Fragment>)
};
export default MyComponent;
And this is the corresponding test:
describe('MyComponent', () => {
let component: ShallowWrapper;
it('should be displayed', () => {
component = shallow(<MyComponent/>);
// HERE I WOULD LIKE TO SET: component.ready = true
expect(component.find('.my-component').length).toBe(1);
});
});
Any idea how to change the ready attribute to true in my test?

This can be accomplished by mocking the react-i18next module with jest.
const mockT = jest.fn((a) => a)
const mockReady = true
jest.mock('react-i18next', () =>
jest.fn().mockImplementation(() => ({
useTranslation: () => ({ t: mockT, ready: mockReady })
}))
);
Take a look at the documentation or this article if you are using ES modules or this answer for other options.
edit: use module factory for second param of jest.mock instead if passing a plain object.

Related

How to render elements inside render prop while testing with Enzyme

I have a component which have this structure
return <Element>
{(a, b): React.ReactElement => (
...some elements here
)}
</Element>;
When I'm testing it I'm getting this with debug
<Element>
[function]
</Element>
My question is what is usually the way to get inside this function to check the elements which it returns?
My test
import React from 'react';
import {createShallow} from '#material-ui/core/test-utils';
import Component from './Component';
let shallow;
function setup() {
const props = {};
const wrapper = shallow(<Component {...props} />);
return {
props,
wrapper,
};
}
beforeAll(() => {
shallow = createShallow({ dive: true });
});
describe('Components', () => {
describe('Element', () => {
it('Should render self', () => {
const {wrapper, props} = setup();
console.log(wrapper.debug())
expect(wrapper.exists()).toBeTruthy();
});
});
});
There are two solutions:
Use wrapper.dive() or wrapper.shallow() that would cause your ShallowWrapper to render child of your top-level component. However, I do not recommend using shallow at all as there are many issues with it and you've just experienced one - it does not render everything.
Use mount instead of shallow and you would have your children function rendered by default.

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)
})
})
})

How to test function that passed from mapDispatchToProps (React/Redux/Enzyme/Jest)

I want to test that function passed from mapDispatchToProps was invoked when button clicking is simulated.
How to test that function which passed from mapDispatchToProps is invoked?
I tried to pass a mocked function by props, but it doesn't work. Any help will be appreciated.
Here below my fake class code and test example.
My component
// All required imports
class App extends React.Component<Props> {
render() {
const { onClick } = this.props;
return (
<>
<h1>Form</h1>
<input />
<button onClick={() => onClick()} />
</>
);
}
}
const mapStateToProps = (state) => {
return {
state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onClick: () => dispatch(actions.onClick())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
My test file
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16/build/index';
import jsdom from 'jsdom';
import React from 'react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import ConnectedApp, { App } from './App';
function setUpDomEnvironment() {
const { JSDOM } = jsdom;
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost/' });
const { window } = dom;
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
}
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
setUpDomEnvironment();
configure({ adapter: new Adapter() });
const mockStore = configureMockStore();
describe('App', () => {
describe('When App connected to store', () => {
describe('When button clicked', () => {
it('should not crush after click on login button', () => {
const onClick = jest.fn()
const store = mockStore(initialStates[1]);
const wrapper = mount(
<Provider store={store}>
<ConnectedApp />
</Provider>);
wrapper.find('button').simulate('click');
??? how to test that function passed from mapDispatchToProps was fired?
});
});
});
});
I recommend following the approach described in the docs and export the connected component as the default export for use in the application, and export the component itself as a named export for testing.
For the code above export the App class and test the click like this:
import * as React from 'react';
import { shallow } from 'enzyme';
import { App } from './code';
describe('App', () => {
it('should call props.onClick() when button is clicked', () => {
const onClick = jest.fn();
const wrapper = shallow(<App onClick={onClick} />);
wrapper.find('button').simulate('click');
expect(onClick).toHaveBeenCalledTimes(1);
});
});
shallow provides everything that is needed for testing the component itself. (shallow even calls React lifecycle methods as of Enzyme v3)
As you have found, to do a full rendering of the component requires a mock redux store and wrapping the component in a Provider. Besides adding a lot of complexity, this approach also ends up testing the mock store and all child components during the component unit tests.
I have found it much more effective to directly test the component, and to export and directly test mapStateToProps() and mapDispatchToProps() which is very easy since they should be pure functions.
The mapDispatchToProps() in the code above can be tested like this:
describe('mapDispatchToProps', () => {
it('should dispatch actions.onClick() when onClick() is called', () => {
const dispatch = jest.fn();
const props = mapDispatchToProps(dispatch);
props.onClick();
expect(dispatch).toHaveBeenCalledWith(actions.onClick());
});
});
This approach makes unit testing the component very simple since you can pass the component props directly, and makes it very simple to test that the component will be handed the correct props by the pure functions (or objects) passed to connect().
This ensures that the unit tests are simple and targeted. Testing that connect() and redux are working properly with the component and all of its child components in a full DOM rendering can be done in the e2e tests.

Shallow test React branch Jest and Enzyme

There are three things I want to figure out. For now I am using shallow render. I use Enzyme and Jest.
I am wondering how I can test the branches in my React component. I
would like to test both sides of the if-else statement (? :). And I
do not want to pull it out in an own function.
How can I check if this.props.myFuncFromProps(value) is been called
when the input changes?
What is the best practice to test mapStateToProps and
mapDispatchToProps?
Here is an example of how my component would look like:
import React from 'react';
import MyChildComponent from 'wherever'; // This component is an input field in this example
export class MyComponent extends React.Component {
render() {
const myFunc(value) {
this.props.myFuncFromProps(value);
}
return (
<div>
{ this.props.isTrue ?
<MyChildComponent
value={this.props.value}
onChange={(value) => myFunc(value)}
/>
: null
}
</div>
);
}
}
To test the different states just render your component with the different attributes and make a snapshot (note, that you have to check the first time the snapshot was created). To test event callback, you have to pass a spy function (jest.fn()) into to component and use simulate to call the event, then test that the spy was called.
describe('MyComponent', () => {
describe('with isTrue is true', () => {
let myComponent
let myFuncFromProps
beforeEach(() => {
myFuncFromProps = jest.fn()
myComponent = shallow(
<MyComponent isTrue myFuncFromProps={myFuncFromProps} />
)
})
it('renders correct', () => {
expect(myComponent).matchSnapshot()
})
it('onchange will call myFuncFromProps', () => {
myComponent
.find('MyChildComponent')
.simulate('onChange', 'someValue')
expect(myFuncFromProps).toHaveBeenCalledWith('someValue')
})
})
it('with isTrue is false it renders correct', () => {
const myComponent = shallow(<MyComponent />)
expect(myComponent).matchSnapshot()
})
})

Resources