Is there a way to use jest.mock's in React? For example, say I want to re-use a component (e.g. Storybook) but want to mock an import / implementation detail for that original React component. Is this possible, and if so, how? Thanks!
sampleComponent.js:
import React from 'react';
import sampleFn from './sampleFn';
const SampleComponent = () => <h1>{sampleFn()}</h1>; // expected output: 'Hello World'
export default SampleComponent;
sampleMockComponent.js:
import React from 'react';
import SampleMockComponent from './sampleComponent';
jest.mock('./sampleFn');
const SampleMockComponent = () => <SampleComponent />; // expected output: 'Testing Mock'
sampleFn.js:
export default () => 'Hello World';
__mocks__/sampleFn.js:
export default () => 'Testing Mock';
If you want to mock for reasons that are not for testing.
I would not suggest you use jest mocks.
Go for a dependency injection pattern.
import React from 'react';
import sampleFn from './sampleFn';
// Unless sample is supplied, sampleFn will be used by default
const SampleComponent = () => {
const { sample = sampleFn } = props;
return <h1>{sample()}</h1>;
}
export default SampleComponent;
Related
I have one component that that use one customized hook and I need to write some test and make sure I mock the hook. How can I mock the customized hook to have unit test for my component like the code of hook does not even exist?
import "./styles.css";
import useAPICall from "#src/hooks/useAPICall ";
export default function App() {
const { onAPICall } = useAPICall(123);
const handleOnClick = useCallback(() => {
onAPICall();
});
return (
<div className="App">
<button onClick={handleOnClick}>Click</button>
</div>
);
}
///test.tsx all I know is this. but I am not sure how I can use mockImplementation that does not return anything
jest.mock('#src/hooks/useAPICall', () => ({
onAPICall: () => jest.fn(),
}));
You can do it in three simple steps:
Import the module which you want to mock
Then mock the module
Provide the return value of the mocked module.
import React from 'react';
import { render } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import App from './App';
import useAPICall from "#src/hooks/useAPICall "; // 1st step
jest.mock('#src/hooks/useAPICall'); //2nd step
test('can call useAPICall hook', () => {
useAPICall.mockReturnValue({ onAPICall: jest.fn() }); // 3rd step
const { getByRole } = render(<NewApp />);
userEvent.click(getByRole('button'));
expect(useAPICall).toBeCalled();
});
I want to write an integration test to assert that a when a parent component drills certain values or properties to a child component, that component receives said values and renders them properly. Below I have two component examples and an example test. Of course, the test is not accurate, but I'm wondering how I can use enzyme to accomplish this? Thanks!
sampleComponent.js:
import React from 'react';
const SampleComponent = () => (
<div test-attr="div">
<SampleChildComponent title="Sample title" />
</div>
);
export default SampleComponent;
sampleChildComponent.js:
import React from 'react';
const SampleChildComponent = ({ title }) => <h3 test-attr="h">{title}</h3>;
export default SampleChildComponent;
sampleComponent.test.js:
import React from 'react';
import { shallow } from 'enzyme';
import SampleComponent from './sampleComponent';
import SampleChildComponent from './sampleChildComponent';
test('renders component without errors', () => {
const wrapper = shallow(<SampleComponent />);
const childWrapper = shallow(<SampleChildComponent />);
expect(childWrapper.text()).toEqual('sample title');
});
To render child components you should use mount instead of shallow:
import { mount } from 'enzyme'
import React from 'react'
import SampleChildComponent from './sampleChildComponent'
import SampleComponent from './sampleComponent'
test('renders component without errors', () => {
const wrapper = mount(<SampleComponent />)
expect(wrapper.find(SampleChildComponent).text()).toEqual('sample title')
})
I am trying to test my component which is consuming data from context via HOC.
Here is setup:
Mocked context module /context/__mocks__
const context = { navOpen: false, toggleNav: jest.fn() }
export const AppContext = ({
Consumer(props) {
return props.children(context)
}
})
Higher OrderComponent /context/withAppContext
import React from 'react'
import { AppContext } from './AppContext.js'
/**
* HOC with Context Consumer
* #param {Component} Component
*/
const withAppContext = (Component) => (props) => (
<AppContext.Consumer>
{state => <Component {...props} {...state}/>}
</AppContext.Consumer>
)
export default withAppContext
Component NavToggle
import React from 'react'
import withAppContext from '../../../context/withAppContext'
import css from './navToggle/navToggle.scss'
const NavToggle = ({ toggleNav, navOpen }) => (
<div className={[css.navBtn, navOpen ? css.active : null].join(' ')} onClick={toggleNav}>
<span />
<span />
<span />
</div>
)
export default withAppContext(NavToggle)
And finally Test suite /navToggle/navToggle.test
import React from 'react'
import { mount } from 'enzyme'
beforeEach(() => {
jest.resetModules()
})
jest.mock('../../../../context/AppContext')
describe('<NavToggle/>', () => {
it('Matches snapshot with default context', () => {
const NavToggle = require('../NavToggle')
const component = mount( <NavToggle/> )
expect(component).toMatchSnapshot()
})
})
Test is just to get going, but I am facing this error:
Warning: Failed prop type: Component must be a valid element type!
in WrapperComponent
Which I believe is problem with HOC, should I mock that somehow instead of the AppContext, because technically AppContext is not called directly by NavToggle component but is called in wrapping component.
Thanks in advance for any input.
So I solved it.
There were few issues with my attempt above.
require does not understand default export unless you specify it
mounting blank component returned error
mocking AppContext with __mock__ file caused problem when I wanted to modify context for test
I have solved it following way.
I created helper function mocking AppContext with custom context as parameter
export const defaultContext = { navOpen: false, toggleNav: jest.fn(), closeNav: jest.fn(), path: '/' }
const setMockAppContext = (context = defaultContext) => {
return jest.doMock('../context/AppContext', () => ({
AppContext: {
Consumer: (props) => props.children(context)
}
}))
}
export default setMockAppContext
And then test file ended looking like this
import React from 'react'
import { shallow } from 'enzyme'
import NavToggle from '../NavToggle'
import setMockAppContext, { defaultContext } from '../../../../testUtils/setMockAppContext'
beforeEach(() => {
jest.resetModules()
})
describe('<NavToggle/>', () => {
//...
it('Should have active class if context.navOpen is true', () => {
setMockAppContext({...defaultContext, navOpen: true})
const NavToggle = require('../NavToggle').default //here needed to specify default export
const component = shallow(<NavToggle/>)
expect(component.dive().dive().hasClass('active')).toBe(true) //while shallow, I needed to dive deeper in component because of wrapping HOC
})
//...
})
Another approach would be to export the component twice, once as decorated with HOC and once as clean component and create test on it, just testing behavior with different props. And then test just HOC as unit that it actually passes correct props to any wrapped component.
I wanted to avoid this solution because I didn't want to modify project file(even if it's just one word) just to accommodate the tests.
Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}
I am using Mocha, Chai, Nock, Sinon, Webpack for Unit tests.
I used the following link to test my actions, reducers
http://redux.js.org/docs/recipes/WritingTests.html
I was able to test dumb components from other react articles. Currently I am trying to test my smart component and i get errors.
my react-redux component (SmartComponent.js)
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import * as componentActions from '../actions/componentActions'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
export class SmartComponent extends Component {
constructor(props) {
super(props)
this.updateText = this.updateText.bind(this)
}
updateText(event){
this.props.actions.updateText(event.target.value)
}
render() {
return(
<div>
<input type='text' onChange={this.action1} placeholder='type something'
/>
<span>{this.props.inputText}
</div>
)
}
}
function mapStateToProps(state) {
const inputText = state.inputText
return{
inputText
}
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(componentActions, dispatch)
}
}
const SmartComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SmartComponent)
module.exports = SmartComponentContainer
this is my unit test file
import setupDom from '../setup'
import chai from 'chai'
import jsxChai from 'jsx-chai'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import TestUtils from 'react-addons-test-utils'
import {Provider} from 'react-redux'
import {findAllWithType, findWithType, findWithClass} from 'react-shallow-testutils'
import SmartComponentContainer,{SmartComponent} from "../containers/SmartComponent"
chai.use(jsxChai)
let expect = chai.expect
/**
* Mock out the top level Redux store with all the required
* methods and have it return the provided state by default.
* #param {Object} state State to populate in store
* #return {Object} Mock store
*/
function createMockStore(state) {
return {
subscribe: () => {},
dispatch: () => {},
getState: () => {
return {...state};
}
};
}
function setup() {
const storeState={
inputText : ''
}
let renderer = TestUtils.createRenderer()
renderer.render(<SmartComponentContainer store={createMockStore(storeState)} />)
let output = renderer.getRenderOutput()
return {
output,
renderer
}
}
function setUpComponent(){
const props ={}
let renderer = TestUtils.createRenderer()
renderer.render(<SmartComponent {...props} />)
let output = renderer.getRenderOutput()
return {
output,
renderer
}
}
describe('test smart component container',()=>{
it('test input value change',()=>{
const {output,renderer} = setup()
console.log(output)
// when i do the console.log output here , i am getting my actions and state variable but i do not get the html inside render method
})
it('test component',()=>{
const {output,renderer} = setUpComponent()
console.log(output)
})
})
The first test is successful and i prints the output with actions and state variables but not able to get render html printed.
The second test throws an error "TypeError: Cannot read property 'propTypes' of undefined" . I want access to html and have ability test like with normal component
expect(output.props.children[0].type).to.equal('input')