How can I pass the context and extract the data in a helper method?
See the below snippet:
import AppContext from '../../context/AppContext'
import extractDatta from '../../helper';
class App extends Component{
static contextType = AppContext
componentWillMount(){
const resolvedData = extractData("/home",this.context)
}
render(){
return(
)
}
}
helper/index.js:
const extractData = (path, context) => {
// getting context as undefined
}
App.test.js:
describe('App test suites', () => {
let wrapper;
let appContext;
beforeEach(() => {
appContext = {name: "Application"}
wrapper = shallow(<Supplier />, { appContext })
})
it('Should render the App Component', () => {
expect(wrapper).toMatchSnapshot();
})
})
Any help appreciated :)
So here is my hacky workaround whilst the Enzyme team work on finishing [https://github.com/airbnb/enzyme/issues/1553](React 16 support). I'm providing the legacy contextTypes on the component class, allowing the context to be passed as per the docs.
import PropTypes from "prop-types";
describe('App test suites', () => {
let wrapper;
let appContext;
beforeEach(() => {
appContext = {name: "Application"}
// Hack in the legacy context API just for tests. You'll get a warning, but it
// ought to work...
Supplier.contextTypes = {
name: PropTypes.string
}
wrapper = shallow(<Supplier />, { appContext })
})
it('Should render the App Component', () => {
expect(wrapper).toMatchSnapshot();
})
})
That probably wouldn't help. But shouldn't you pass Options object with context parameter name instead of appContext?
wrapper = shallow(<Supplier />, { context: appContext })
Related
I have the following component that loads data from an external API using the useEffect hook on mount:
import React, { useEffect, useState } from "react";
import Dropdown from "./common/Dropdown";
import { getWeapons } from "../services/gear";
import { DropdownOptionType } from "./common/Dropdown";
const EMPTY_OPTION = {
key: "0",
label: "",
value: "null"
};
const Gear = () => {
const [weaponOptions, setWeaponOptions] = useState([EMPTY_OPTION]);
const [weapon, setWeapon] = useState("null");
useEffect(() => {
const fetchWeaponsOptions = async (): Promise<void> => {
const weaponsData = await getWeapons();
const newWeaponOptions: DropdownOptionType[] = [
EMPTY_OPTION,
...weaponsData.map(({ id, name }) => {
return {
key: id,
label: name,
value: id
};
})
];
setWeaponOptions(newWeaponOptions);
};
fetchWeaponsOptions();
}, []);
// TODO add weapon dropdown on change, selected weapon state
const handleWeaponChange = ({ value }: DropdownOptionType): void => {
setWeapon(value);
};
return (
<div>
<h2>Gear:</h2>
<Dropdown
defaultValue={weapon}
label="Weapon"
name="weapon"
options={weaponOptions}
onChange={handleWeaponChange}
/>
</div>
);
};
export default Gear;
I am mounting this component inside my App Component:
import React from "react";
import Stats from "./components/Stats";
import Gear from "./components/Gear";
const App = () => {
return (
<div className="App">
<h1>Untitled combat game character creator</h1>
<Stats />
<Gear />
</div>
);
};
export default App;
I have the following test for my App component:
import React from "react";
import { render } from "#testing-library/react";
import App from "./App";
test("renders app title", () => {
const { getByText } = render(<App />);
const titleElement = getByText(/Untitled combat game character creator/i);
expect(titleElement).toBeInTheDocument();
});
The test is passing but outputs the following warning:
PASS src/App.test.tsx ● Console
console.error node_modules/react-dom/cjs/react-dom.development.js:530
Warning: An update to Gear inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at ...
in Gear (at App.tsx:11)
in div (at App.tsx:8)
in App (at App.test.tsx:6)
I tried wrapping the test code inside the act callback, but I am getting the same warning:
test("renders app title", () => {
act(() => {
const { getByText } = render(<App />);
const titleElement = getByText(/Untitled combat game character creator/i);
expect(titleElement).toBeInTheDocument();
});
});
And:
test("renders app title", () => {
let titleElement;
act(() => {
const { getByText } = render(<App />);
titleElement = getByText(/Untitled combat game character creator/i);
});
expect(titleElement).toBeInTheDocument();
});
But I am getting the same warning in both cases. What would be the correct way to handle this?
When I create a test for my connected React component where I want to test the mapStateToProps logic I run into a problem which I'm not sure how to solve.
Error message
Expected: 1
Received: undefined
24 | it('should show previously rolled value', () => {
25 | // test that the state values were correctly passed as props
> 26 | expect(wrapper.props().lastRolledNumber).toBe(1);
When I check the wrapper.props() it only returns the store object and no properties.
To make sure it's not my code I have found an example that should work to simplify it, but I get the same problem when using that exact version in my app (option #2, https://jsramblings.com/2018/01/15/3-ways-to-test-mapStateToProps-and-mapDispatchToProps.html)
I think it might have to do with the React 16+ version, which I found mentioned here: https://airbnb.io/enzyme/docs/api/ReactWrapper/props.html
.props() => Object
Returns the props object for the root node of the wrapper. It must be
a single-node wrapper. This method is a reliable way of accessing the
props of a node; wrapper.instance().props will work as well, but in
React 16+, stateless functional components do not have an instance.
See .instance() => ReactComponent
Does anyone know how to test this in a good way to see that the logic is working as expected without exporting the private mapStateToProps function directly?
Component
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// Component 1 - "Base component"
// Exporting it is a good practice for testing its own logic
export const Dice = ({ lastRolledNumber, onRollDice }) => (
<div>
<p>The last rolled number was {lastRolledNumber}.</p>
<button onClick={onRollDice}>Roll dice</button>
</div>
);
Dice.propTypes = {
lastRolledNumber: PropTypes.number.isRequired,
onRollDice: PropTypes.func.isRequired
}
const mapStateToProps = (state) => ({
lastRolledNumber: state.lastRolledNumber
});
const mapDispatchToProps = (dispatch) => ({
onRollDice: () => dispatch({ type: 'ROLL_DICE' })
});
// Component 2 - Container component
// Export it as a default export
export default connect(mapStateToProps, mapDispatchToProps)(Dice);
Test
import React from 'react';
import { shallow } from 'enzyme';
import '../test-config'; // Setup Enzyme & Adapter
import DiceContainer from './Dice';
// Create the mock store
import configureMockStore from 'redux-mock-store';
const mockStore = configureMockStore();
describe('Dice', () => {
let wrapper, store;
beforeEach(() => {
const initialState = {
lastRolledNumber: 1
};
store = mockStore(initialState);
// Shallow render the container passing in the mock store
wrapper = shallow(
<DiceContainer store={store} />
);
});
it('should show previously rolled value', () => {
// test that the state values were correctly passed as props
expect(wrapper.props().lastRolledNumber).toBe(1);
});
it('should roll the dice again when button is clicked', () => {
// test that the component events dispatch the expected actions
wrapper.simulate('rollDice');
const actions = store.getActions();
expect(actions).toEqual([ { type: 'ROLL_DICE' } ]);
});
});
You are almost there. You need to call .dive() method so that you can get the Dice component rather than the DiceContainer component which is wrapped by the connect HOC of react-redux module.
Short answer:
wrapper = shallow(<DiceContainer store={store} />).dive();
A Completed working example:
index.jsx:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
export const Dice = ({ lastRolledNumber, onRollDice }) => (
<div>
<p>The last rolled number was {lastRolledNumber}.</p>
<button onClick={onRollDice}>Roll dice</button>
</div>
);
Dice.propTypes = {
lastRolledNumber: PropTypes.number.isRequired,
onRollDice: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
lastRolledNumber: state.lastRolledNumber,
});
const mapDispatchToProps = (dispatch) => ({
onRollDice: () => dispatch({ type: 'ROLL_DICE' }),
});
export default connect(mapStateToProps, mapDispatchToProps)(Dice);
index.test.jsx:
import React from 'react';
import { shallow } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import DiceContainer from '.';
const mockStore = configureMockStore();
describe('Dice', () => {
let wrapper;
let store;
beforeEach(() => {
const initialState = {
lastRolledNumber: 1,
};
store = mockStore(initialState);
wrapper = shallow(<DiceContainer store={store} />).dive();
});
it('should show previously rolled value', () => {
expect(wrapper.props().lastRolledNumber).toBe(1);
});
it('should roll the dice again when button is clicked', () => {
wrapper.simulate('rollDice');
const actions = store.getActions();
expect(actions).toEqual([{ type: 'ROLL_DICE' }]);
});
});
Unit test results:
PASS src/stackoverflow/59771991/index.test.jsx (9.645s)
Dice
✓ should show previously rolled value (19ms)
✓ should roll the dice again when button is clicked (2ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 11.505s
Test coverage html report:
In my case, I was able to write the test with render by this -
import React from 'react';
import '#testing-library/jest-dom';
import {
screen, cleanup, render,
} from '#testing-library/react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
const store = configureMockStore()({
headerState: {
showBack: false,
},
});
const mockProps = { ... };
describe('Component', () => {
beforeAll(() => {
render(
<Provider store={store}>
<Component {...mockProps} />
</Provider>,
);
});
afterAll(cleanup);
test('Test', () => {
screen.debug();
});
});
I want to start my React microapp with props I'm passing from Single SPA (customProps). The only way I've figured out is:
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import App from './where/my/root/is.js';
function domElementGetter() {
return document.getElementById("mounting-node")
}
let EnhancedRootComponent = App; /* 1 */
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: EnhancedRootComponent, /* 1 */
domElementGetter,
})
export const bootstrap = [
(args) => {
/* 2 */ EnhancedRootComponent = () => <App myArgs={args.thePropsIWannaPass} />;
return Promise.resolve();
},
reactLifecycles.bootstrap,
];
export const mount = [reactLifecycles.mount];
export const unmount = [reactLifecycles.unmount];
This does work (I can see and use the passed props in my component) but I'm not completely OK with the fact that the root component changes in between calling singleSpaReact (1) and calling bootstrap(2). Would there be side effects to this that I'm not seeing now? Does anyone know a better approach for this?
You have this value inside the props variable without this reassign.
Check this out:
Root-config.js, file responsible for passing prop to microfrontend
import { registerApplication, start } from 'single-spa';
import * as isActive from './activity-functions';
registerApplication('#company/micro2', () => System.import('#company/micro2'), isActive.micro2);
registerApplication('#company/micro1', () => System.import('#company/micro1'), isActive.micro1, { "authToken": "test" });
start();
micro1 Root.tsx
import React from 'react';
export default class Root extends React.Component {
constructor(props: any){
super(props)
}
state = {
hasError: false,
};
componentDidCatch() {
this.setState({ hasError: true });
}
render() {
console.log(this.props)
return (
<div>test</div>
);
}
}
console.log output:
props:
authToken: "test" <---- props which you pass
name: "#company/micro1"
mountParcel: ƒ ()
singleSpa: {…}
__proto__: Object
for more advance usage
const lifecycles = singleSpaReact({
React,
ReactDOM,
loadRootComponent: (props) =>
new Promise((resolve, reject) => resolve(() =>
<Root {...props} test2={'test2'}/>)),
domElementGetter,
});
I tried using react-hooks-testing-library but it dosn't seem how handle hooks that use useContext.
import React,{useContext} from 'react'
import {AuthContextData} from '../../AuthContext/AuthContext'
const useAuthContext = () => {
const {authState} = useContext(AuthContextData)
const {isAuth,token,userId,userData} = authState
return {isAuth,token,userId,userData}
}
export default useAuthContext
You have to wrap your hook in a context provider:
let authContext
renderHook(() => (authContext = useAuthContext()), {
wrapper: ({ children }) => (
<AuthContextData.Provider value={/* Your value */}>
{children}
<AuthContextData.Provider>
)
})
Let's say you have a component where you call the useContext(context) hook to get a key isLoading that should be false or true.
If you want to test useContext in a component you could test it as follow:
const context = jest.spyOn(React, 'useContext');
if each test in the same file need to have different context values, then inside your test, you can mock the implementation like this:
context.mockImplementationOnce(() => {
return { isLoading: false };
});
or outside the tests for all tests to have same context:
context.mockImplementation(() => {
return { isLoading: false };
});
Hope it helps.
Having very simple component:
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
I want to test if doSth is called when shouldDoSth is equal true.
I've written a test:
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
but it seems that although I pass doSth as props it gets overridden by mapDispatchToProps as console.log('im not a mock') is executed.
How to properly pass/override/assign doSth function to make component use mock instead of function from mapDispatchToProps. Or maybe I'm doing something which should not be allowed at all and there is 'proper' way of testing my case. Shall I just mock dispatch instead and check if it is called with proper arguments?
I think one thing you need to figure out is whether you want doSth to be a prop, or a redux action connected in mapDispatchToProps.
If it's a prop, then you would connect it to redux in a parent (container). Remove it from this component's mapDispatchToProps. This would make the component more testable.
If you want it to be a redux action connected in this component, then it would make sense to move the action out of this component, somewhere like actions.js, import it in this component, and then mock it in the test jest.mock('actions.js', () => ({doSth: jest.mock()}))
Export the unconnected component and use it in the test and you will be able to override the mapDispatchToProps action.
export class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
import {MyComponent} from '../MyComponent'
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
I think that you should ask yourself if you want to test the unconnected MyComponent or the connected one.
Here you have two discussions that may help you: Am I testing connected components correclty? and Can't reference containers wrapped in a Provider or by connect with Enzyme
If you are not testing the action nor state properly said, you might forget about mapStateToProps and mapDispatchToProps (those processes are already tested by redux) and pass values through props.
Check the following example:
describe('MyComponent', () => {
let wrapper;
const doSthMock = jest.fn();
beforeEach(() => {
const componentProps = {
doSth: true,
};
wrapper = mount(
<MyComponent
{... componentProps}
doSth={doSthMock}
/>
);
});
it('+++ render the component', () => {
expect(wrapper.length).toEqual(1);
});
it('+++ call doSth when shouldDoSth', () => {
expect(doSthMock).toHaveBeenCalled();
});
})