I have created my custom component NumberInput field. I am new in writing test cases so I just try to write one simple test case and want to execute it successfully.
Here is my component
import React from 'react';
import PropTypes from 'prop-types';
import NumberFormat from 'react-number-format';
import TextField from 'components/TextField';
function CustomInput(props) {
return <TextField {...props} />;
}
function NumberInput(props) {
const { onChange, ...otherProps } = props;
return (
<NumberFormat
thousandSeparator
decimalSeparator="."
decimalScale={2}
{...otherProps}
customInput={CustomInput}
onValueChange={values => {
const { value } = values;
onChange(value);
}}
/>
);
}
NumberInput.propTypes = {
onChange: PropTypes.func,
};
export default NumberInput;
and I am trying to write a test case for that
import React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { NumberInput } from '../index';
describe('<NumberInputField />', () => {
it('Expect to have unit tests specified', () => {
const { container } = render(<NumberInput />);
const NumberFormat = container.firstChild
fireEvent.change(NumberFormat, { target: { value: 10 } });
expect(NumberFormat.value).toBe(10);
//expect(true).toEqual(false);
});
});
I am trying to write a test case using
Jest
testing-library/react
this is My Error
You are importing NumberInput as a named export, but it is actually a default export.
Change import { NumberInput } from '../index'; to import NumberInput from '../index';
OR
Change your export from export default NumberInput; to export { NumberInput };
Related
I want to make setLoading function from "Screen" to be available in "HomeScreen". So
context.js
import { createContext } from "react";
export const LoadingContext = createContext();
screen.js
This is screen component (parent component for HomeScreen). And I want to control the state of Modal in HomeScreen (child component).
import { Modal, StyleSheet, View } from "react-native";
import { useMemo, useState } from "react";
import { LoadingContext } from "../../context";
const Screen = ({ children}) => {
const [loading, setLoading] = useState(false);
const loadingContext = useMemo(() => {
return { setLoading: setLoading };
}, [loading]);
return (
<LoadingContext.Provider value={loadingContext}>
<View>
{children}
</View>
</LoadingContext.Provider>
);
};
export default Screen;
HomeScreen.js
import { View, Text } from "react-native";
import { LoadingContext } from "../../context";
import { useContext} from "react";
const HomeScreen = ({ navigation }) => {
const context = useContext(LoadingContext);
console.log(context); // undefined
return (
<Screen>
<Text></Text>
</Screen>
);
};
export default HomeScreen;
The reason is your useContext isn't inside your Screen component. Read the docs,
The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.
Your useContext looks up the contexts in parent nodes but fails and hence the undefined value.
Try rapping the HomeScreen with Screen component when you are using it which I am assuming is your router.
EDIT:
Here's your fix
import { View, Text } from "react-native";
import { LoadingContext } from "../../context";
import { useContext} from "react";
const HomeScreen = ({ navigation }) => {
const context = useContext(LoadingContext);
console.log(context); // undefined
return <Text></Text>;
};
const WrappedHomeScreen = (props) => {
return <Screen><HomeScreen {...props} /></Screen>;
}
export default WrappedHomeScreen;
Below is my store which will retrieve observable units from an API by using the loadUnits method, the API is working well, I also added a console.log() here, I can see the first element's value.
import { ITutorialUnit } from "./../model/unit";
import { action, observable } from "mobx";
import { createContext } from "react";
import agent from "../api/agent";
class UnitStore {
#observable units: ITutorialUnit[] = [];
#observable title = "hello from mobx";
#observable loadingInitial = false;
#action loadUnits = () => {
this.loadingInitial = true;
agent.TutorialUnits.list()
.then(units => {
units.forEach((unit) => {
this.units.push(unit);
});
})
.then(() => console.log("from store:" + this.units[0].content)) // I can see the data from this logging
.finally(() => (this.loadingInitial = false));
};
}
export default createContext(new UnitStore());
then when I want to use the observable units in App.tsx :
import React, { Fragment, useContext, useEffect, useState } from "react";
import { Container } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import NavBar from "../../features/nav/NavBar";
import { ActivityDashboard } from "../../features/Units/dashboard/tutorialUnitDashboard";
import UnitStore from "../stores/unitStore";
import { observer } from "mobx-react-lite";
import { LoadingComponent } from "./LoadingComponent";
import agent from "../api/agent";
import { ITutorialUnit } from "../model/unit";
const App = () => {
const unitStore = useContext(UnitStore);
const units = unitStore;
useEffect(() => {
unitStore.loadUnits();
console.log("from App.tsx: " + units); // will only log a undefined
}, [unitStore]);
if (unitStore.loadingInitial) {
return <LoadingComponent content="Loading activities..." />;
}
return (
<Fragment>
<NavBar />
<Container style={{ marginTop: "7em" }}>
<h1>{unitStore.title}</h1>
<ActivityDashboard />
</Container>
</Fragment>
);
};
export default observer(App);
I can't get the observable units's data, but I can get another observable title's data, which is just a string, the LoadingComponent is not working either.
please help me!
I figured this out. the version of Mobx should below 6.0.0, i use 5.10.1 and everything's just fine.
Consider the following react component.
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { connect } from "react-redux";
import formFields from "components/Login/formFields";
import LoginField from "components/Login/LoginField/LoginField";
import _ from "lodash";
import { loginFormSubmit } from "store/actions/profile/profile";
import { SubmissionError } from "redux-form";
export class Login extends Component {
renderFields() {
return _.map(formFields, ({ label, name }) => {
return (
<Field
component={LoginField}
type="text"
key={label}
label={label}
name={name}
/>
);
});
}
render() {
const { handleSubmit, history } = this.props;
return (
<div>
<form
onSubmit={handleSubmit(values =>
loginFormSubmit(values, history, SubmissionError)
)}
>
{this.renderFields()}
<button type="submit">Send the Survey</button>
</form>
</div>
);
}
}
export default connect(
null,
{ loginFormSubmit }
)(
reduxForm({
form: "loginform"
})(Login)
);
You can see the handleSubmit is called when the form is submitted. handleSubmit calls our custom loginFormSubmit from redux. how can I check loginFormSubmit is called within handleSubmit. Here is my test so far
import { Login } from "components/Login/Login";
import { shallow } from "enzyme";
import React from "react";
describe("The Login component description", () => {
describe("The Login component", () => {
const props = {
handleSubmit: jest.fn()
};
it("should call handleSubmit on form submission", () => {
const wrapper = shallow(<Login {...props} />);
wrapper.find("button").simulate("click");
expect(props.handleSubmit).toHaveBeenCalled();
});
});
});
The module the function is imported from should be mocked at the top of the test:
import { loginFormSubmit } from "store/actions/profile/profile";
jest.mock('store/actions/profile/profile', () => ({ loginFormSubmit: jest.fn() }));
Then it can be asserted:
expect(props.handleSubmit).toHaveBeenCalledWith(expect.any(Function));
expect(loginFormSubmit).not.toHaveBeenCalled(...);
props.handleSubmit.mock.calls[0][0]()
expect(loginFormSubmit).toHaveBeenCalledWith(...);
So i haven't been able to get the todos passed down from the parent component nor have i been able to successfully set them myself. I'm not trying to test anything in particular at this point. Just that the component 'exists' without error
Component code:
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class TodoList extends Component {
renderTodos = () => {
const { todos } = this.props;
return todos.map(todo => {
return <TodoItem key={todo.id} {...todo} />;
});
};
render() {
return (
<div>
{this.renderTodos()}
</div>
);
}
}
export default TodoList;
Test code:
import React from 'react';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
import TodoList from './TodoList';
import TodoItem from './TodoItem';
describe(TodoList, () => {
const todos = [
{
id: 1,
text: 'Walk the walk'
},
{
id: 2,
text: 'Talk the talk'
}
];
const component = shallow(<TodoList todos={todos} />);
it('should exist', () => {
const component = renderer.create(<TodoList />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Please help. New to Jest and pretty new to testing in general.
When you create a shallow instance of your component you need to pass the props which you are using without conditional check in your component
const component = shallow(<TodoList todos={[]}/>);
and
const component = renderer.create(<TodoList todos={[]}/>);
I've got fairly simple react component (Link wrapper which adds 'active' class if route is active):
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
const NavLink = (props, context) => {
const isActive = context.router.isActive(props.to, true);
const activeClass = isActive ? 'active' : '';
return (
<li className={activeClass}>
<Link {...props}>{props.children}</Link>
</li>
);
}
NavLink.contextTypes = {
router: PropTypes.object,
};
NavLink.propTypes = {
children: PropTypes.node,
to: PropTypes.string,
};
export default NavLink;
How am I supposed to test it? My only attempt was:
import NavLink from '../index';
import expect from 'expect';
import { mount } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
It doesn't work and returns TypeError: Cannot read property 'isActive' of undefined. It definitely needs some router mocking, but I have no idea how to write it.
Thanks #Elon Szopos for your answer but I manage to write something much more simple (following https://github.com/airbnb/enzyme/pull/62):
import NavLink from '../index';
import expect from 'expect';
import { shallow } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const context = { router: { isActive: (a, b) => true } };
const renderedComponent = shallow(<NavLink to="/home" />, { context });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
I have to change mount to shallow in order not to evaluate Link which gives me an error connected with the react-router TypeError: router.createHref is not a function.
I would rather have "real" react-router than just an object but I have no idea how to create it.
For react router v4 you can use a <MemoryRouter>. Example with AVA and Enzyme:
import React from 'react';
import PropTypes from 'prop-types';
import test from 'ava';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter as Router } from 'react-router-dom';
const mountWithRouter = node => mount(<Router>{node}</Router>);
test('submits form directly', t => {
const onSubmit = sinon.spy();
const wrapper = mountWithRouter(<LogInForm onSubmit={onSubmit} />);
const form = wrapper.find('form');
form.simulate('submit');
t.true(onSubmit.calledOnce);
});
Testing components which rely on the context can be a little tricky. What I did was to write a wrapper that I used in my tests.
You can find the wrapper below:
import React, { PropTypes } from 'react'
export default class WithContext extends React.Component {
static propTypes = {
children: PropTypes.any,
context: PropTypes.object
}
validateChildren () {
if (this.props.children === undefined) {
throw new Error('No child components were passed into WithContext')
}
if (this.props.children.length > 1) {
throw new Error('You can only pass one child component into WithContext')
}
}
render () {
class WithContext extends React.Component {
getChildContext () {
return this.props.context
}
render () {
return this.props.children
}
}
const context = this.props.context
WithContext.childContextTypes = {}
for (let propertyName in context) {
WithContext.childContextTypes[propertyName] = PropTypes.any
}
this.validateChildren()
return (
<WithContext context={this.props.context}>
{this.props.children}
</WithContext>
)
}
}
Here you can see a sample usage:
<WithContext context={{ location: {pathname: '/Michael/Jackson/lives' }}}>
<MoonwalkComponent />
</WithContext>
<WithContext context={{ router: { isActive: true }}}>
<YourTestComponent />
</WithContext>
And it should work as you would expect.
You can use https://github.com/pshrmn/react-router-test-context for that exact purpose
"Create a pseudo context object that duplicates React Router's context.router structure. This is useful for shallow unit testing with Enzyme."
After installing it, you will be able to do something like
describe('my test', () => {
it('renders', () => {
const context = createRouterContext()
const wrapper = shallow(<MyComponent />, { context })
})
})