I am currently trying to unit test a container that pulls in a static JSON file of phone numbers and passes it to the component to display, however I am not sure how I should go about testing it. The code for the container is as follows:
import React from 'react';
import data from *JSON file location*
import CountryInfo from *component for the country information* ;
class CountryInfoContainer extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
numbersJson: null
};
}
async componentWillMount() {
const numbersJson = data;
this.setState({ numbersJson });
}
render() {
return (
<CountryInfo json={this.state.numbersJson} showText={this.props.showText} />
);
}
}
export default CountryInfoContainer;
I currently have my unit test to look like this
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { mount, configure } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import CountryInfoContainer from './CountryInfoContainer';
configure({ adapter: new Adapter() });
describe('Successful flows', () => {
test('checks if json has null entries', () => {
const wrapper = mount(<MemoryRouter><CountryInfoContainer /></MemoryRouter >);
const data = wrapper.find(numbersJson);
// eslint-disable-next-line no-console
console.log(data.debug);
});
});
Obviously, it doesn't work now because I am not sure how to use the variable numbersJson in the container in the test file or how to check if it is null.
The variable numbersJson is not defined in the scope of your test. If I understand correctly, you are testing that when you first mount the component, that it's state contains a null value for the numbersJson key.
First of all, you need to mount your component directly without MemoryRouter:
const wrapper = mount(<CountryInfoContainer />);
Then you can write an expect() for the state:
expect(wrapper.state().numbersJson).toBeNull();
Related
I'm testing for state within a Create-React-App with Enzyme. How can I pass this test?
When my App component is rendered in my test it is wrapped in
<BrowserRouter>
(attempting to mount it otherwise yields a
Invariant failed: You should not use <Route> outside a <Router>
error in the test).
Shallow wrapping yields
TypeError: Cannot read property 'state' of null
as does mounting and wrapping with
<BrowserRouter>.
I have tried this
Result: Question unanswered
this
Result: Question unanswered
this
Result: Uninstalling react-test-renderer made no difference
and this
Result: I checked the state in my component and it is defined.
console.log(wrapper.instance().state) yields the same error: 'null'
App.js:
class App extends Component {
//#region Constructor
constructor(props) {
super(props);
this.state = {
//... other correctly formatted state variables
specificRankingOptionBtns: false
}
app.test.js:
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import App from '../../App'
import renderer from 'react-test-renderer'
import { shallow, mount } from 'enzyme';
describe('App', () => {
fit('renders App.js state correctly', () => {
const wrapper = mount(<BrowserRouter><App /></BrowserRouter>);
//console.log(wrapper.instance().state);
//const wrapper = shallow(<App />);
//const wrapper = mount(<App />);
console.log(wrapper.instance().state);
//const wrapper = mount(shallow(<BrowserRouter><App />
//</BrowserRouter>).get(0));
expect(wrapper.state('specificRankingOptionBtns')).toEqual(false);
});
}
Expect: test to pass
Actual: "TypeError: ReactWrapper::state("specificRankingOptionBtns") requires that state not be null or undefined"
I had the same issue. This worked for me.
import { MemoryRouter as Router } from 'react-router-dom';
it('should find MaskedTextInput in LoginPage', () => {
const mountWithRouter = node => mount(<Router>{node}</Router>);
const wrapper = mountWithRouter(<LoginPage {...mockedProps} />);
const maskedInput = wrapper.find('MaskedTextInput');
const componentInstance = wrapper
.childAt(0)
.childAt(0)
.instance(); // could also be instance
const mountedState = componentInstance.state.passwordInputType;
expect(mountedState).toEqual('password');
});
I would like to show data from a single API to different components as I want to hit the API only once and distribute the data to multiple small components. I know I can do this by using redux state but not sure how to do it. Need your help to achieve this. Below is the code done so far.
homepage/index.js
import SlidingBanner from './banner/BannerList';
import Celebslider from './celebrityslider/CelebSlider';
class HomePage extends Component {
render() {
return (
<div>
<SlidingBanner />
<anotherslider />
</div>
);
}
}
export default HomePage;
BannerList.js
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { itemsFetchData } from '../../../actions/items';
class BannerList extends Component {
componentDidMount() {
this.props.fetchData();
}
render() {
let bannerArray = [];
let banner = this.props.items.banner
for (let key in banner) {
bannerArray.push(banner[key]);
return (
<div>
<Slider {...slidersettings}>
{this.props.items.banner.map((item) => (
<div key={item.id}>
<img src={item.image_url} className="img-responsive"/>
</div>
))}
</Slider>
</div>
);
}
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (null);
}
}
BannerList.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.object.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(BannerList);
anotherslider.js
Now in this file, i want to fetch another array of objects or object from the same API.
I tried to mount the API in container component but did not worked, I hope i am doing some mistake. Please correct.
If you want to fetch data in anotherslider.js file you must connect reducer to class/function inside it as well as you are making it in BannerList.js file.
Now before render call componentWillReceiveProps(nextProps) function and you will get your data here.
If you want to call data in both of the sliders, you have 2 ways to handle it.
Make your redux requests in HomePage.js component and bind the data to the other components.
When you get the data on BannerList.js component, your state will be updated. Just add the redux connection to your anotherslider.js component and get data when updated.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeList);
Apart from all these options, you can also use react's Context API as Provider/consumer to distribute your data among small components... this will save you passing props to all small components and directly access the value in component using Context.Consumer .. moreover if you do not want to store this state in global redux store, context API will save you from it...
I was egzecuting tutorial:
CLICK
And I am getting error: this.props.data is undefined.
I was implementing the tutorial in my test application, where I was testing also various React tools, so I have not copy-pasted it in 100%. I am using ASP.NET Core MVC and React, own architecture (for test application) and I did not installed all npm's from the tutorial. But I belive, that it is syntax or architecture problem. I am guessing, that calling server's data is corrupted somehow in app.js or CommentBox.js.
Error from console:
TypeError: this.props.data is undefined[Więcej informacji] bundle.js line 541 > eval:45:17
The above error occurred in the <CommentList> component:
in CommentList (created by CommentBox)
in div (created by CommentBox)
in CommentBox (created by App)
in div (created by App)
in div (created by App)
in App
Consider adding an error boundary to your tree to customize error handling behavior.
react-dom.development.js:14226
[Przełącz szczegóły wiadomości] TypeError: this.props.data is undefined[Więcej informacji]
Main app.js file that returns to index.js:
(...)
return (
<div className="App">
<div className="App-header">
Welcome to React!
<AddProject addProject={this.handleAddProject.bind(this)}/>
<Projects onDelete={this.handleDeleteProject.bind(this)} projects={this.state.projects} />
<CommentBox url="/comments" pollInterval={2000}/>
</div>
</div>
);
(...)
In my component folder all parent and children files:
CommentBox.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
import CommentList from '../components/CommentList';
import CommentForm from '../components/CommentForm';
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = { data: this.props.initialData };
}
loadCommentsFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
componentDidMount() {
this.loadCommentsFromServer();
window.setInterval(() => this.loadCommentsFromServer(), this.props.pollInterval);
}
render() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm/>
</div>
);
}
}
export default CommentBox;
CommentList.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
import Comment from '../components/Comment';
class CommentList extends React.Component {
render() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment name={comment.name} key={comment.productID}>
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
}
export default CommentList;
Comment.js:
import React, { Component } from 'react';
import $ from 'jquery';
import uuid from 'uuid';
class Comment extends React.Component {
rawMarkup() {
const md = new (global.Remarkable || window.Remarkable)();
const rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
}
render() {
return (
<div className="comment">
<h2 className="commentName">
{this.props.name}
</h2>
{this.props.children}
</div>
);
}
}
export default Comment;
First, way too much code. Try to be as concise as possible.
Your issue is that this.state.data in CommentBox is undefined / null initially. Make sure that you're passing the initialData prop into CommentBox or handling the null case in CommentList
var commentNodes = (this.props.data || []).map(function (comment) {
return (
<Comment name={comment.name} key={comment.productID}>
</Comment>
);
});
I have a structure of src/resource/file.json.
1.By installing load-json and using require:
class App extends Component{
constructor(props) {
super(props);
this.state = {summaryData: [], sortBy: null};
this.sortContent = this.sortContent.bind(this);
}
componentWillMount() {
require('../resource/file.json')
.then(response => {
// Convert to JSON
return response;
})
.then(findresponse => {
// findresponse is an object
console.log(findresponse);
this.setState({summaryData: findresponse});
})
.catch(norespone => {
console.log('Im sorry but i could not fetch anything');
});
}
And appears the message :
Module not found: Can't resolve '../resource/file.json' in 'C:\Xampp\htdocs\path\to\app\src\pages'
Through myJSON:
request('https://api.myjson.com/bins/{id..}').then(sumres => {
if (sumres) {
this.setState({summaryData: sumres});
console.log(this.state.summaryData);
}
});
}
But nothing appears in the console or the network tab. Cans someone propose a solution?
Is it possible to load the json file without installing a local server?
Yes! It is possible to load JSON into your page. At the top of script where you import your modules, import your json file.
Example:
import React from 'react';
import jsonData from '../resource/file.json';
etc...
And in your case, if you're trying to set it to state, just set the state when the component initializes.
constructor(props) {
super(props);
this.state = {
summaryData: jsonData.myArray,
sortBy: null
};
this.sortContent = this.sortContent.bind(this);
}
Hope this helps!
I'm using jest to test a component with a <Link> from react-router v4.
I get a warning that <Link /> requires the context from a react-router <Router /> component.
How can I mock or provide a router context in my test? (Basically how do I resolve this warning?)
Link.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import { Link } from 'react-router-dom';
test('Link matches snapshot', () => {
const component = renderer.create(
<Link to="#" />
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
The warning when the test is run:
Warning: Failed context type: The context `router` is marked
as required in `Link`, but its value is `undefined`.
You can wrap your component in the test with the StaticRouter to get the router context into your component:
import React from 'react';
import renderer from 'react-test-renderer';
import { Link } from 'react-router-dom';
import { StaticRouter } from 'react-router'
test('Link matches snapshot', () => {
const component = renderer.create(
<StaticRouter location="someLocation" context={context}>
<Link to="#" />
</StaticRouter>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Have a look at the react router docs about testing
I had the same issue and using StaticRouter would still require the context which needed more configuration to have it available in my test, so I ended up using the MemoryRouter which worked very well and without any issues.
import React from 'react';
import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
// SampleComponent imports Link internally
import SampleComponent from '../SampleComponent';
describe('SampleComponent', () => {
test('should render', () => {
const component = renderer
.create(
<MemoryRouter>
<SampleComponent />
</MemoryRouter>
)
.toJSON();
expect(component).toMatchSnapshot();
});
});
The answer of #Mahdi worked for me! In 2023 if you want to test a component that includes <Link> or <NavLink>, we just need to wrap it with the <MemoryRouter> in the test file:
// App.test.js
import { render, screen } from "#testing-library/react";
import MyComponent from "./components/MyComponent";
import { MemoryRouter } from "react-router-dom"; // <-- Import MemoryRouter
test("My test description", () => {
render(
<MemoryRouter> // <-- Wrap!
<MyComponent />
</MemoryRouter>
);
});
my test like this:
import * as React from 'react'
import DataBaseAccout from '../database-account/database-account.component'
import { mount } from 'enzyme'
import { expect } from 'chai'
import { createStore } from 'redux'
import reducers from '../../../reducer/reducer'
import { MemoryRouter } from 'react-router'
let store = createStore(reducers)
describe('mount database-account', () => {
let wrapper
beforeEach(() => {
wrapper = mount(
< MemoryRouter >
<DataBaseAccout store={store} />
</MemoryRouter >
)
})
afterEach(() => {
wrapper.unmount()
wrapper = null
})
})
but I don't konw why MemoryRouter can solve this。
Above solutions have a common default defact:
Can't access your component's instance! Because the MemoryRouter or StaticRouter component wrapped your component.
So the best to solve this problem is mock a router context, code as follows:
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
describe('YourComponent', () => {
test('test component with react router', () => {
// mock react-router context to avoid violation error
const context = {
childContextTypes: {
router: () => void 0,
},
context: {
router: {
history: createMemoryHistory(),
route: {
location: {
hash: '',
pathname: '',
search: '',
state: '',
},
match: { params: {}, isExact: false, path: '', url: '' },
}
}
}
};
// mount component with router context and get component's instance
const wrapper = mount(<YourComponent/>, context);
// access your component as you wish
console.log(wrapper.props(), wrapper.state())
});
beforeAll(() => {
configure({ adapter: new Adapter() });
});
});