so i've been stuck for several days on an issue while implementing Unit Testing and Integration testing in a large production application that was built in Meteor/React tech stack. I am using the meteortesting:mocha package as recommended by the meteor documentation and enzyme.
The issue i am having is that i am not really grasping how i can mock the withTracker functionality. I am trying to use our dev database as the source for the test users and mock data. All of the props are generated in the tracker and then sent to the component it wraps. (Code sample below). Another issue i am having is that we are using meteor:universe for i18n internationalization. When mounting the component it shows plain text instead of the translated content. Wondering if there's a work around. Thanks in advance!
Component I am testing:
import React, { useState } from "react";
import ABCComponent from "./ABCComponent";
import XYZ from "./XYZComponent";
import * as ROUTE_CONSTANTS from "../../global/RoutesConstants";
import { withRouter } from "react-router-dom";
import { withTracker } from "meteor/react-meteor-data";
import UserAssessments from "../../collections/UserAssessments";
import moment from "moment-timezone";
import { i18n } from "meteor/universe:i18n";
const SortDashboard = (props) => {
const [isSkillsSort, setIsSkillSort] = useState(true);
return (
<div>
{/* Contains some logic to set 'isSetSkillSort' state true or false (business logic hidden for security purposes*/}
{isSkillsSort ? (
<ABCComponent user={props.user} skillsSorts={props.skillsSorts} employeeList={props.directReportEmp} />
) : (
<XYZComponent
user={props.user}
importanceSorts={props.importanceSorts}
employeeList={props.directReportEmp}
/>
)}
</div>
);
};
const SortDashboardTracker = withTracker((props) => {
if (!props.user) return {};
const abcSubscription = Meteor.subscribe("abcSubscription");
if (abcSubscription.ready()) {
const rawData = UserAssessments.find(
{ "assessor._id": Meteor.user().profile._id },
{ sort: { updatedDate: -1 } }
).fetch();
rawData.forEach((assessment) => {
//Do Something (business logic hidden for security purposes)
});
}
const xyzSubscription = Meteor.subscribe("xyzSubscription");
let directReportEmp = [];
if (xyzSubscription.ready()) {
directReportEmp = Meteor.users.find({ "profile.managerId": Meteor.user().username }).fetch();
}
return { importanceSorts, skillsSorts, directReportEmp };
})(SortDashboard);
export default withRouter(SortDashboardTracker);
My Test:
import {Meteor} from 'meteor/meteor';
import React from 'react';
import chai from 'chai';
import sinon, { mock } from 'sinon'
import {mount, shallow, configure, render} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {mockManager,mockEmp1,mockEmp2,mockEmp3,mockUser} from '../../mockUsers'
import SortDashboard from '../../../../imports/components/cllWizard/SortDashboard';
import { withRouter, BrowserRouter as Router } from "react-router-dom";
configure({adapter: new Adapter()});
if (Meteor.isClient) {
describe('WizardComponent', ()=> {
//let returnedText
//importing the mock user we created for testing purposes
const currentUser = mockUser
let props = {user: currentUser}
beforeEach(() => {
// now Meteor.user() will return the user we just imported
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser);
// needed in methods
sinon.stub(Meteor, 'userId');
Meteor.userId.returns(currentUser._id);
});
//afterEach specifies that we want to restore the user after running the test
afterEach(() => {
Meteor.user.restore();
Meteor.userId.restore();
});
it('CLIENT: should render the Sort Dashboard', () => {
const wrapper = mount(<Router><SortDashboard.WrappedComponent {...props}/></Router>)
console.log(wrapper.debug())
});
});
}
TLDR;
Need to test a client side component that uses withTracker and withRouter
Need to be able to see the translated text from meteor:universe:i18n in the test
Pulling mock data from the db instead of manually creating it.
The issue may very well be my approach and lack of understanding. Please correct me where-ever necessary. Thanks in advance!
Related
So, this component "Info" is a container which process some data in order to generate a "Details" children component with some props.
Info.JS
import React from 'react'
import Details from './Details/Details'
import {Card} from 'reactstrap'
import classes from './Info.module.css'
import {connect} from 'react-redux'
const Info = (props)=>{
let itemDetails = 'Item Details'
let items = undefined
if(props.activeTab === '1'){
items = props.shortTerm
} else if (props.activeTab ==='2'){
items = props.mediumTerm
} else if (props.activeTab ==='3'){
items = props.longTerm
}
if(items.length!==0){
itemDetails=(
items.map((i,index)=>{
if(i.id===props.itemIndex){
return <Details
title={i.itemName}
desc={i.itemDesc}
date={"Created at "+i.created}
edited={i.lastEdited}
key={index}/>
}
console.log(itemDetails)
return null
})
)
} else{
return itemDetails = (
<Details
title="Title"
desc="Description"
key={null}
date={null}/>
)
}
return(
<Card className={classes.info}>
{itemDetails}
</Card>
)
}
const mapStateToProps = (state) =>{
return{
shortTerm:state.reducer.items.shortTerm,
mediumTerm:state.reducer.items.mediumTerm,
longTerm:state.reducer.items.longTerm,
activeTab:state.reducer.activeTab,
itemIndex: state.reducer.itemIndex
}
}
export default connect(mapStateToProps)(Info)
Question
How can I make a test in which I can check if any component is being rendered? Or, how can I write a test in which I can check if any "itemDetails" is being rendered?
I tried this, so far, to test if I could find any being rendered but it always return me a error saying "Cannot read property 'find' of undefined".
The test code is this one:
Info.test.js
import React from 'react'
import {Provider} from 'react-redux'
import {configure,shallow,mount,render} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'
import Info from './Info'
import Details from './Details/Details'
configure({adapter:new Adapter()})
describe('<Info />',()=>{
let wrapper
beforeEach(()=>{
wrapper= shallow(<Info/>);
})
it('Should return one Details',()=>{
expect(wrapper.find(Details)).toHaveLength(1)
})
});
So I found an answer,based on this one: Testing React Redux - cannot read properties of undefined, or wrapper undefined
It worked perfectly for me! I happens that, to generate the component, I had to pass some props to the component. This is the setup that I used in order to make the test to work:
import React from 'react'
import {Provider} from 'react-redux'
import {configure,shallow,mount,render} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'
import {Info} from './Info'
import Details from './Details/Details'
configure({adapter:new Adapter()})
const setup=()=>{
let props= {
shortTerm:[],
mediumTerm:[],
longTerm:[],
activeTab:'1',
itemIndex:0
}
let wrapper = shallow(<Info {...props}/>);
return {props, wrapper};
};
describe('<Info />',()=>{
const {wrapper}=setup()
it('Should return one Details Component',()=>{
expect(wrapper.find(Details)).toHaveLength(1)
})
});
I am using jest and enzyme library for react testing with create-react-app boilerplate.
With the running of suite and test I am get in above error..
Could not found any solution yet.
Let me know if any solution.
TypeError: Cannot read property 'subroute' of undefined
Yes, I was rendering the connected component with passing the props into it.
So with that purpose all we need to pass store element into the Provider, and mount the component into it.
So all we need to understand is :
Mount: It will render the deep element of props and component associated with it.
Shallow: It will render the the first component of the top layer, not going the deep connected component as I was doing before with shallow.
Here are the code for and complete solution:
import { mountWrap } from '../contextWrap'
import { Provider } from 'react-redux'
import sinon from 'sinon'
import Login from '../components/Login/'
// import makeStore from '../redux/createStore'
import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const mockStore = configureMockStore([ thunk ])
const authDetails = {
'authDetails' : {
Terms :''
}
}
const match = {
params : {}
}
let actionSpy = sinon.spy()
let actionHistorySpy = sinon.stub({})
let authDetails_ = sinon.stub(authDetails)
let store
let component
/* eslint-disable */
describe('tests for MyContainerComponent', () => {
beforeEach(() => {
store = mockStore(authDetails)
component = mountWrap(<Provider store={ store }>
<Login history={actionHistorySpy} match={match} setGlobalLoaderStatus= {actionSpy} userDetail={authDetails_} />
</Provider>)
})
it('renders container', () => {
console.log(component.debug())
})
})
I'm trying to test component method, which inside performing network call to external resources. After reading docs I still can't figure out how to do so. Can anyone help? Here is my code(some parts hidden for brevity):
My component:
import React from 'react'
import ResourceService from '../../modules/resource-service'
export default class SliderComponent extends React.Component {
setActiveSlide = (activeSlide) => {
ResourceService.getData({
id: activeSlide,
}).then((data) => {
if (data) {
this.setState({
data,
})
}
})
}
}
Resource service:
import axios from 'axios'
export default class ResourceService {
static getData(params) {
return axios.post('/api/get_my_data', params)
.then((resp) => resp.data)
}
}
Desired test (as I understand it):
import React from 'react'
import { mount, configure } from 'enzyme'
import SliderComponent from '../../../app/components/slider'
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
const wrapper = mount(
<SliderComponent />
);
wrapper.instance().setActiveSlide(1);
// some state checks here
});
I need mock ResourceService.getData call inside SliderComponent, and I really can't understand ho to do it...
You can import your ResourceService in your test and mock the method getData with jest.fn(() => ...). Here is an example:
import React from 'react'
import { mount, configure } from 'enzyme'
import ResourceService from '../../../modules/resource-service'
import SliderComponent from '../../../app/components/slider'
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
// you can set up the return value, you can also resolve/reject the promise
// to test different scnarios
ResourceService.getData = jest.fn(() => (
new Promise((resolve, reject) => { resolve({ data: "testData" }); }));
const wrapper = mount(<SliderComponent />);
wrapper.instance().setActiveSlide(1);
// you can for example check if you service has been called
expect(ResourceService.getData).toHaveBeenCalled();
// some state checks here
});
try using axios-mock-adapter to mock the postreq in your test.
It should look something like this (may need a few more tweaks):
import React from 'react'
import { mount, configure } from 'enzyme'
import SliderComponent from '../../../app/components/slider'
import axios from'axios';
import MockAdapter = from'axios-mock-adapter';
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
let mock = new MockAdapter(axios)
//you can define the response you like
//but your params need to be accordingly to when the post req gets called
mock.onPost('/api/get_my_data', params).reply(200, response)
const wrapper = mount(
<SliderComponent />
);
wrapper.instance().setActiveSlide(1);
// some state checks here
});
make sure to check the docs of axios-mock-adapter
I am writing a test spec for the component that uses browserHistory. It throws an error
ReferenceError: navigator is not defined
i tried solution from Mocha-Chai throws "navigator not defined" due to React-router component but it is still not working. May be I am not being able to use the solution in right way.
Here is my spec file.
import React from 'react';
import { expect } from 'chai';
import jsdom from 'jsdom';
import sinon from 'sinon';
import shallow from 'enzyme';
import { MyComponent } from 'component.jsx';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.document = doc;
global.window = doc.defaultView;
global.navigator = {
userAgent: 'node.js',
};
describe('<Component />', () => {
let wrapper;
before(() => {
sinon.stub(Component.prototype, 'componentWillMount');
wrapper = shallow(<Component />);
});
context('Component', () => {
it('should render component', () => {
expect(wrapper.type()).to.equal('div');
});
});
after(() => {
Component.prototype.componentWillMount.restore();
});
});
Any help will be appreciated. Thank you.
Here is component.jsx file
import React, { Component, PropTypes } from 'react';
import R from 'ramda';
import { browserHistory } from 'react-router';
export class MyComponent extends Component {
componentWillMount() {
const query = this.props.location.query;
// looping through the query object of url
R.mapObjIndexed((value, key) => this.prepareStateData(value, key), query);
}
componentDidUpdate(prevProps) {
// this push the url every time the component is updated
if (this.props.urlHistory && this.props.urlHistory !== prevProps.urlHistory) {
browserHistory.push(this.props.urlHistory);
}
}
prepareStateData(value, key) {
// this changes the state according to the url
switch (key) {
case 'query1': {
// do something
break;
}
case 'query2': {
// do something
break;
}
default:
// do something
break;
}
}
render() {
return (
<div>
{ /* render part */ }
</div>
);
}
}
MyComponent.propTypes = {
location: PropTypes.object,
urlHistory: PropTypes.string,
};
Your test runner doesn't know anything about the environment that your app is supposed to run in, so window.navigator/window.location etc are not available.
browserHistory that you're using requires browser environment to work correctly and I assume that this is the issue you're facing. Try replacing imported browserHistory with createMemoryHistory and see if the test passes.
Excerpts from the docs which shows the difference:
browserHistory uses the HTML5 History API when available, and falls back to full refreshes otherwise. browserHistory requires additional configuration on the server side to serve up URLs, but is the generally preferred solution for modern web pages.
createMemoryHistory creates an in-memory history object that does not interact with the browser URL. This is useful for when you need to customize the history object used for server-side rendering, for automated testing, or for when you do not want to manipulate the browser URL, such as when your application is embedded in an .
I've tried various ways but I can't seem to find anything about specifically testing if an action creator is called from a component. The hard part seems to be getting enzyme and redux connected containers to work. Below I'm exporting both connected and component class for tests. I tried wrapping the connected one in a provider but then I don't know how to target the textarea from enzyme to trigger the simulated onChange. The below works but I'm not sure if it's the best way of doing this?
Simple container:
I want to assert that onChange will call sendCommandValueChange with the correct value.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sendCommandValueChange } from 'actions';
export class RobotCommand extends Component {
handleCommandChange(e){
this.props.sendCommandValueChange(e.target.value);
}
render(){
return (
<div>
<textarea
onChange={ e => this.handleCommandChange(e)}
/>
</div>
);
}
}
export default connect(null, { sendCommandValueChange })(RobotCommand);
and here is the test:
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { shallow, mount } from 'enzyme';
import { RobotCommand } from './RobotCommand';
describe('RobotCommand', () => {
it('should call sendCommandValueChange action creator on value change', () => {
const props = { sendCommandValueChange: sinon.spy() };
const wrapper = shallow(<RobotCommand {...props} />);
wrapper.find('textarea').simulate('change', {target: {value: 'foo'}});
expect(props.sendCommandValueChange.calledWith('foo')).to.equal(true);
});
});