I am new to Jestjs and enzyme framework and I am trying to write test cases for a particular react component and I am little stuck.
export class ProductDetailsForm extends Component{
handleMetaDataDefinition = e => {
const { value, name } = e.target;
if (name === "xmlVersion") {
this.checkSpecialCharacters(value);
}
this.setState(prevState => ({
...prevState,
[name]: value
}));
this.props.setProductDetailsFormValue({
...this.props.productDetailsForm,
[name]: value
});
};
checkSpecialCharacters = value => {
if (!value || value.match(/^[a-zA-Z0-9._-]+$/)) {
this.setState(() => ({ error: '' }));
} else {
this.setState(() => ({
error: `Special characters and operators such as !##$%^&*()+{}:;?|\\[]'"= are not allowed`
}));
}
}
render(){
return(
<div>
<MetaDataDefinition
readOnly={false}
metaData={this.state}
handleMetaDataDefinition={this.handleMetaDataDefinition}
validateVersion={this.validateVersion}
/>
</div>
);
}
}
I have started with the test case, but I am stuck and unable to proceed how to work on the function handleMetaDataDefinition for full coverage including the function checkSpecialCharacters. Below is the code that I started to write for ProductDetailsForm.test.js
let wrapper;
beforeEach(() => {
wrapper = shallow(
<ProductDetailForm />
);
});
test("should call handleMetaDataDefinition", ()=> {
wrapper.find('MetaDataDefinition').props('handleMetaDataDefinition');
});
I have used some part of my actual code and not the whole code, as I need help in this specific part only to write test case for handleMetaDataDefinition and checkSpecialCharacters methods.
There're two possible option how to write your tests.
You can trigger validation from your MetaDataDefinition component and pass there needed data.
test("should call handleMetaDataDefinition", ()=> {
const component = wrapper.find('MetaDataDefinition');
fillYourComponentSomehow();
triggerAnEventSomehow();
/*For example component.find('button').simulate('click');
wrapper.update();// We can wait for updating state differently(if needed i'll take a look to doc.)
expect(wrapper.state()).toBe(stateThatYouExpect);
});
Or you can test it as 'black box'
test("should call handleMetaDataDefinition", ()=> {
const component = wrapper.find('MetaDataDefinition');
component.props().handleMetaDataDefinition(objectForMethod)
wrapper.update();
expect(wrapper.state()).toBe(stateThatYouExpect);
});
If you have HOCs around your component you'll need to find this component by class name
wrapper.find('ProductDetailsForm')
UPDATE
You can test it like
let wrapper;
let setProductDetailsFormValue;
beforeEach(() => {
setProductDetailsFormValue = jest.fn();
wrapper = shallow(
<ProductDetailForm setProductDetailsFormValue={setProductDetailsFormValue} />
);
});
test("should call handleMetaDataDefinition", ()=> {
const testObject = { target: {name: 'xmlVersion', value: '!!!123asd!'}, }
const component = wrapper.find('MetaDataDefinition');
component.props().handleMetaDataDefinition(testObject)
wrapper.update();
expect(wrapper.state().error).toBe('Special characters and operators such as !##$%^&*()+{}:;?|\\[]'"= are not allowed');
expect(wrapper.state()[xmlVersion]).toBe('!!!123asd!');
expect(setProductDetailsFormValue).toBeCalledWith({
[xmlVersion]: '!!!123asd!',
...other fields})
});
Related
I have a component with functionality which validates input element onblur and adds error class if validation fails:
TableRows = (props )=>(
return <input class="inputElement" onBlur="this.validate()" />
)
validate function is as follows:
validate = async ({ target }: ChangeEvent<HTMLInputElement>) => {
try {
const element = target;
this.setState({ loading: true });
const { value } = target;
const match = value.match(/^\d+(?:\.\d{0,2})?$/g);
if (!match || match.length === 0) {
element.className += ' inputError';
} else {
element.className = target.className.replace(' inputError', '');
}
const { data: updatedValues } = await sendforSaving({inputValue: value});
this.setState({ newValues: data });
} finally {
this.setState({ loading: false });
}
};
I am trying to write a unit test with enzyme as follows:
it('should add error class on invalid input onblur', () => {
const mockVal4Test = {
localCurrency: 'USD',
value: '20.02.1',
} as any;
component = shallow(
<TableRows {...defaultProps} initialValues={mockVal4Test} currencyType={CurrencyType.LOCAL} />
);
const myInput = component.find('.inputElement');
myInput.simulate('blur');
expect(saleTarget.hasClass('inputError')).toBe(true);
});
I get the myInput element but after simulating blur I am expecting the 'validate' function to be called and error class "inputError" to be added to the element.
I used a mock event for blur event to pass to simulate. And later the same mock event is used to check the changes, as the blur event changes the passed event object. Here is my solution.
it('should add error class on invalid input for StoreTarget input on blur', () => {
component = shallow(
<TableRows {...defaultProps} initialValues={mockVal4Test} currencyType={CurrencyType.LOCAL} />
)
const mockedEvent = {
target: {
value: '1.2.1.2',
className:'inputClass'
}
} as any;
const myInput = component.find('. inputElement');
myInput.simulate('blur', mockedEvent );
expect(mockedEvent.target.className.includes('inputError')).toBe(true);
});
I am testing this connected component:
export class ExclusiveSelectboxesFormSectionView extends React.Component<Props> {
handleSelection = (field: Field, message: string) => {
this.props.setCard({ message, field });
};
cancelSelection = () => this.props.setCard({ message: null, field: null });
render() {
return (
<View>
{this.props.cancelTitle && (
<CheckBox
title={this.props.cancelTitle}
checked={this.props.card.field == null}
onPress={this.cancelSelection.bind(this)}
checkedIcon="dot-circle-o"
uncheckedIcon="circle-o"
/>
)}
{this.props.fields.map((field, i) => {
const props = {};
props.isSelected = this.props.card.field == field;
props.selectionHandler = this.handleSelection.bind(this);
return <ExclusiveFieldView field={field} key={i} {...props} />;
})}
</View>
);
}
}
const mapState = ({ currentFormReducer }) => {
const card = currentFormReducer.card || { message: null, field: null };
return { card };
};
const mapDispatch = { setCard };
export default connect(mapState, mapDispatch)(ExclusiveSelectboxesFormSectionView);
I'm trying to test the non-connected component using react-native-testing-library. The component works in the app, but this test is failing to find "Second field option 2" in the next-to-last assertion in the test.
// non-connected component
import { ExclusiveSelectboxesFormSectionView } from "../../src/components/ExclusiveSelectboxesFormSectionView";
function createWrapper(customProps) {
let mockCard = { message: null, field: null };
const props = {
fields,
setCard: jest.fn().mockImplementation((card: Types.Card) => {
mockCard = card;
}),
card: mockCard,
...customProps
};
wrapper = render(
<Fragment>
<ExclusiveSelectboxesFormSectionView fields={fields} {...props} />
</Fragment>
);
return wrapper;
}
describe("ExclusiveSelectboxesFormSectionView", () => {
let checkboxes;
beforeEach(() => {
wrapper = createWrapper();
checkboxes = wrapper.getAllByType(CheckBox);
expect(checkboxes.length).toBe(3);
});
fit("shows the value of the currently selected field", async () => {
await fireEvent.press(checkboxes[1]); // show options
await fireEvent.press(wrapper.getByText("Second field option 2")); // select option
const component = wrapper.getByType(ExclusiveSelectboxesFormSectionView);
expect(component.props.setCard).toHaveBeenCalled();
// options should be gone
expect(wrapper.queryByText("Second field option 1")).toBeNull();
// selected option should still be on screen
expect(wrapper.getByText("Second field option 2")).toBeDefined();
expect(checkboxes[1].props.checked).toBe(true);
});
});
I've passed in a card prop and a setCard mock function prop, in place of redux providing these.
The mock setCard function is being called, so I think the problem is that the component is not rerendering with its new props (and a newly set card prop). A log statement in the component's render function confirms this (it only prints once when the test is run).
I imagine there's something basic I'm missing about how I'm rendering the component, or wrapping it, or calling it, or something.
Can anyone spot my problem?
It looks like react-native-testing-library's update function does the trick. But it was a bit tough to figure out.
// refactored from createWrapper
function getWrapperProps() {
return {
fields,
setCard: jest.fn().mockImplementation((card: Types.Card) => {
mockCard = card;
}),
card: mockCard
};
}
function createWrapper(customProps) {
wrapper = render(
<Fragment>
<ExclusiveSelectboxesFormSectionView
{...getWrapperProps()}
{...customProps}
/>
</Fragment>
);
return wrapper;
}
function updateWrapper(customProps) {
wrapper.update(
<Fragment>
<ExclusiveSelectboxesFormSectionView
{...getWrapperProps()}
{...customProps}
/>
</Fragment>
);
checkboxes = wrapper.getAllByType(CheckBox);
}
// call updateWrapper() when you need to get the newly rendered props
it("shows the value of the currently selected field", async () => {
await fireEvent.press(checkboxes[1]);
await fireEvent.press(wrapper.getByText("Second field option 2"));
const component = wrapper.getByType(ExclusiveSelectboxesFormSectionView);
expect(component.props.setCard).toHaveBeenCalled();
updateWrapper();
// options should be gone
expect(wrapper.queryByText("Second field option 1")).toBeNull();
// selected option should still be on screen
expect(wrapper.getByText("Second field option 2")).toBeDefined();
expect(checkboxes[1].props.checked).toBe(true);
});
Now it passes.
I'd love to know of other options. I'm not clear why this had been working with an earlier implementation, although the earlier implementation used state in the component, which I'm sure is basically the answer.
Good afternoon,
I have a component file structured like that globally :
class Component ...
render(){
const {array} = this.props
{!array.includes(value) ?
(<View ...props
id="myComponent"/>
....
</View>) :
(<View ...props
id="myOtherComponent"/>
....
</View>)
}
}
And in my test file, i'm doing the stuff like that :
describe('Testing Component', () => {
test('conditional rendering', () => {
const wrapper = shallow(<Component array={[value]}/>);
expect(wrapper.find(n => n.prop('id') === "myOtherComponent").exists(true))
});
});
But even if I modify the props sent for the array, it always returned me true... What's the keyword to check that the nested component is actually verified and rendered...
I think the error is in your expect argument.
I would use the findWhere function instead of find;
The exists method should not receive a parameter in this
case, as it only receives Enzyme's Selectors and not booleans (you can read more about it here);
Add a toBeTruthy call to the expect line.
Here's a similar situation to yours that we have a test for and it works just fine:
it('tests name', () => {
const mockComponent = shallow(<Component {...props} />);
const textNode = mockComponent.findWhere(n => n.text() === props.name);
expect(textNode.exists()).toBeTruthy();
});
So your test would end up looking like this:
describe('Testing Component', () => {
test('conditional rendering', () => {
const wrapper = shallow(<Component array={[value]}/>);
const node = wrapper.findWhere(n => n.prop('id') === 'myOtherComponent');
expect(node.exists()).toBeTruthy();
});
});
Say I have the following component:
export class ExampleComponent extends Component {
exampleMethod1 = () => {
console.log('in example 1')
}
exampleMethod2 = () => {
console.log('in example 2')
this.exampleMethod1()
}
render() {
return (
<TouchableOpacity id='touchable' onPress={exampleMethod2}><Text>Touchable</Text></TouchableOpacity>
)
}
}
This works exactly how you would expect. The button appears, and can be pressed. Both methods fire, and console log their text.
I now try to test this with jest:
describe('example tests', () => {
let wrapper
beforeEach(() => {
wrapper = shallow(<ExampleComponent/>)
})
it('this test fails. Interestingly both messages still print', () => {
const instance = wrapper.instance()
instance.exampleMethod2 = jest.fn()
wrapper.find('#touchable').simulate('press')
//wrapper.update() uncommenting this line has no effect.
expect(instance.exampleMethod2.mock.calls.length).toBe(1)
})
it('this test passes. Only the first message prints', () => {
const instance = wrapper.instnace()
instance.exampleMethod1 = jest.fn()
wrapper.find('#touchable').simulate('press')
expect(instance.exampleMethod1.mock.calls.length).toBe(1)
})
})
As annotated, the first test fails, and the original message prints, as if I had never mocked out the method. This happens irrespectively of whether wrapper.update() is run or not.
Interestingly, if we replace the onPress with a seemingly identical arrow function like so:
onPress={() => {exampleMethod2()}}
The test suddenly passes. This whole thing suggest some weird this binding shenanigans (I think?). Any explanation as to what is going on would be much appreciated!
If you want to test custom methods on component's prototype object, you should use mount function from enzyme and use spyOn to mock and trace the call to that method.
export class ExampleComponent extends Component {
exampleMethod1 = () => {
console.log('in example 1');
this.setState({ example1: true });
}
exampleMethod2 = () => {
console.log('in example 2')
this.exampleMethod1()
}
render() {
return (
<TouchableOpacity id='touchable' onPress={exampleMethod2}><Text>Touchable</Text></TouchableOpacity>
)
}
}
describe('example tests', () => {
let wrapper
beforeEach(() => {
wrapper = mount(<ExampleComponent/>)
})
afterAll(() => { wrapper = null; })
it('some desc here', () => {
const instance = wrapper.instance();
spyOn(instance, 'exampleMethod1').and.callThrough();
expect(instance.setState).toHaveBeenCalled();
});
})
I'm trying to test a React component using Mocha and Enzyme that uses a dynamic import to load a module.
When I try to test the logic that relies on the dynamic import I get incorrect results. The problem is that the async functions don't finish before the test finishes so I can never get accurate results.
How can I handle this scenario?
Component
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
// styles
import styles from './PasswordStrengthIndicator.scss';
class PasswordStrengthIndicator extends React.Component {
static defaultProps = {
password: undefined,
onPasswordChange: undefined,
}
static propTypes = {
password: PropTypes.string,
onPasswordChange: PropTypes.func,
}
constructor() {
super();
this.state = {};
}
componentWillMount() {
this.handlePasswordChange();
}
componentWillReceiveProps(nextProps) {
const password = this.props.password;
const nextPassword = nextProps.password;
if (password !== nextPassword) {
this.handlePasswordChange();
}
}
render() {
const strength = this.state.strength || {};
const score = strength.score;
return (
<div className={ styles.passwordStrength }>
<div className={ classNames(styles.score, styles[`score-${score}`]) } />
<div className={ styles.separator25 } />
<div className={ styles.separator50 } />
<div className={ styles.separator75 } />
</div>
);
}
// private
async determineStrength() {
const { password } = this.props;
const zxcvbn = await import('zxcvbn');
let strength = {};
if (password) strength = zxcvbn(password);
return strength;
}
async handlePasswordChange() {
const { onPasswordChange } = this.props;
const strength = await this.determineStrength();
this.setState({ strength });
if (onPasswordChange) onPasswordChange(strength);
}
}
export default PasswordStrengthIndicator;
Test
describe('when `password` is bad', () => {
beforeEach(() => {
props.password = 'badpassword';
});
it.only('should display a score of 1', () => {
const score = indicator().find(`.${styles.score}`);
expect(score.props().className).to.include(styles.score1); // should pass
});
});
I was able to accomplish this with a -- something.
I switched the test that relies on the dynamic import to be asynchronous. I then created a function that renders the component and returns a promise that dynamically imports the module I'm trying to import in the component.
const render = () => {
indicator = shallow(
<PasswordStrengthIndicator { ...props } />,
);
return (
Promise.resolve()
.then(() => import('zxcvbn'))
);
};
I believe this is relying on the same concept as just waiting since import('zxcvbn') will take a similar enough amount of time to import in both places.
Here's my test code:
describe('when `password` is defined', () => {
describe('and password is bad', () => {
beforeEach(() => {
props.password = 'badpassword';
});
it('should display a score of 1', (done) => {
render()
.then(() => {
const score = indicator.find(`.${styles.score}`);
expect(score.props().className).to.include(styles.score1);
done();
});
});
});
});
This ended up working out because it prevented me from having to stub and I didn't have to change my component's implementation to better support stubbing. It is also less arbitrary than waiting x ms.
I'm going to leave this question open for now as there are probably better solutions that the community can provide.
Theres a couple of ways to solve this, the simple cheap and dirty way is to delay the expectation for a reasonable amount of time. This turns the test into an async test so you will need to use the done method after your assertion to tell mocha that the test is complete...
it('should display a score of 1', (done) => {
setTimeout(() => {
const score = indicator().find(`.${styles.score}`);
expect(score.props().className).to.include(styles.score1);
done() // informs Mocha that the async test should be complete, otherwise will timeout waiting
}, 1000) // mocha default timeout is 2000ms, so can increase this if necessary
});
The other more involved way would be to stub the call to import with something like Sinon and manually return a resolved promise with the dynamically loaded component.
Must admit I haven't tried stubbing a webpack method before so may be more trouble than usual. Try the simple version and see how it goes.