I have a react class without anything to render. It has few function which uses const name = { this.context } inside a function. However, name (in below example) is always undefined. I am not sure what I am doing wrong.
I have tried setting context wrap.setContenxt({name: true}) but still when the function is called it shows this.context as {}.
person.js
export class Person extends Component {
constructor(props) {
super(props);
}
someMethod() {
const { name } = this.context;
const { record } = this.props;
if (record && name) {
this.setState({
awesome: true
});
}
}
componentDidMount() {}
render() {
return null
}
}
test.js
describe("this.context test", () => {
const props = { record: true }
const context = { name: false }
it('should test someMethod()', () => {
const wrap = shallow( <Person {...props} />, {context})
const instance = wrap.instance()
instance.someMethod();
expect(instance.state.awesome).toBe(true) // pass
})
});
I expect that it should not set state since name is false
Update:
One of thing I missed from documentation is:
The root component you are rendering must have a contextTypes static property.
Based on that if I update test.js as follow, it works:
describe("this.context test", () => {
// ***this was missing***
Person.contextTypes = {
name: PropTypes.boolean
};
const props = { record: true }
const context = { name: false }
it('should test someMethod()', () => {
const wrap = shallow( <Person {...props} />, {context})
const instance = wrap.instance()
instance.someMethod();
expect(instance.state.awesome).toBe(true) // fail
})
});
Related
Per documentation, Hooks cannot be used inside class components. But there are ways with higher order components: How can I use React hooks in React classic `class` component?. However this answer provided does not address the case of hooks that get called on function invocation. Take this simple Toast hook from: https://jossmac.github.io/react-toast-notifications/. I'd like to call the hook inside of a class of form:
```
class MyClass extends React.Component {
onTapButton = () => {
if(conditionTrue){
addToast('hello world', {
appearance: 'error',
autoDismiss: true,
})
}
}
render(){ ... }
}
```
There'd be no way of calling addToast without using const { addToast } = useToasts() in the class method, which would throw error.
You can use withToastManager HOC to archive that work
Here is an example
import React, { Component } from 'react';
import { withToastManager } from 'react-toast-notifications';
class ConnectivityListener extends Component {
state = { isOnline: window ? window.navigator.onLine : false };
// NOTE: add/remove event listeners omitted for brevity
onlineCallback = () => {
this.props.toastManager.remove(this.offlineToastId);
this.offlineToastId = null;
};
offlineCallback = id => {
this.offlineToastId = id;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
const { isOnline } = this.state;
if (prevState.isOnline !== isOnline) {
return { isOnline };
}
return null;
}
componentDidUpdate(props, state, snapshot) {
if (!snapshot) return;
const { toastManager } = props;
const { isOnline } = snapshot;
const content = (
<div>
<strong>{isOnline ? 'Online' : "Offline"}</strong>
<div>
{isOnline
? 'Editing is available again'
: 'Changes you make may not be saved'}
</div>
</div>
);
const callback = isOnline
? this.onlineCallback
: this.offlineCallback;
toastManager.add(content, {
appearance: 'info',
autoDismiss: isOnline,
}, callback);
}
render() {
return null;
}
}
export default withToastManager(ConnectivityListener);
For more information you can also find here
I am beginner in react unit testing with enzyme/jest,
I want to test my logic inside componentWillMount method.
I want to test based on my context object whether redirect happens or not based on my business logic
class ActivateSF extends Component {
constructor(props) {
super(props);
this.className = 'ActivateSF.js'
this.state = {
messages: null,
}
}
render() {
return (
<SDPActivateInterstitialUI
context={this.props.context}
messages={this.state.messages}
/>
);
}
componentWillMount() {
let context = this.props.context
if(!context.userInfo){
return this.callIdentify(context)
}
let externalLP = ExternalLandingPageUtil.getExternalLandingPageUrl(context);
if (externalLP) {
window.location.replace(`${externalLP}`);
return;
}
if (context.userInfo)
{
console.log("user identified prior to activation flow")
if (UserInfoUtil.isSubsribedUser(context))
{
window.location = '/ac'
}
else
{
this.callPaymentProcess(context)
}
}
}
You can try beforeEach to mount and in your test you call .unmount and perform your test on it.
beforeEach(() => {
const myComponent= mount(<MyComponent myprop1={...} />);
});
describe('<MyComponent/>', () => {
it('actually unmounts', () => {
...
...
myComponent.unmount();
... Do unmount tests here
});
});
Example straight from the enzyme docs: https://airbnb.io/enzyme/docs/api/ShallowWrapper/unmount.html
import PropTypes from 'prop-types';
import sinon from 'sinon';
const spy = sinon.spy();
class Foo extends React.Component {
constructor(props) {
super(props);
this.componentWillUnmount = spy;
}
render() {
const { id } = this.props;
return (
<div className={id}>
{id}
</div>
);
}
}
Foo.propTypes = {
id: PropTypes.string.isRequired,
};
const wrapper = shallow(<Foo id="foo" />);
expect(spy).to.have.property('callCount', 0);
wrapper.unmount();
expect(spy).to.have.property('callCount', 1);
I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.
I'm trying to do some unit testing using jest/enzyme for my react components.
But I'm facing problems with an function I'm passing to a second component.
I don't understand if I have to test or mock this function. If I have to mock it, I don't know how to do that for a function.
Parent Component
export default class Parent extends Component {
togglePosition (term, event) {
this.setState({
top: term.length >= 3
})
}
render () {
return (
<div>
<Child togglePosition={this.togglePosition} />
</div>
)
}
}
Child component
export default class Child extends Component {
handleChange (event) {
const term = event.target.value
this.props.togglePosition(term) // <-- Test/mock it?
this.setState({
loading: 'loading',
term
})
}
render () {
return (
<div>
<Input id="target-input" onChange={this.handleChange} />
</div>
)
}
}
This is how I do a test for the Child component - testing handleChange:
Unit test (Child)
it('handleChange() should set state.term', () => {
const event = { target: { value: 'test' } }
const wrapper = shallow(<Child />)
wrapper.find('#target-input').simulate('change', event)
const state = wrapper.instance().state
expect(state).toEqual({ loading: 'loading', term: 'test' })
})
Do get this error: TypeError: this.props.togglePosition is not a function
Without actually testing it, I believe this is what you need:
it('handleChange() should set state.term', () => {
const togglePosition = jest.fn();
const event = { target: { value: 'test' } };
const wrapper = shallow(<Child togglePosition={togglePosition} />);
wrapper.find('#target-input').simulate('change', event);
const state = wrapper.instance().state;
expect(state).toEqual({ loading: 'loading', term: 'test' });
expect(togglePosition).toHaveBeenCalledWith('test');
})
Mock the passed function: const togglePosition = jest.fn();, and test the condition/response: expect(togglePosition).toHaveBeenCalledWith('test');.
I'm not really sure why this happens, I have a very simple Component and test, however, it fails on ✖ should call getState on TestStore. But as getStateFromStores DOES get called, it should be called as well, right? I am clueless atm.
import React, { PropTypes } from 'react'
import TestStore from '../stores/TestStore'
export default class TestComponent extends React.Component {
static propTypes = {
}
static getStateFromStores() {
return TestStore.getState()
}
constructor(props) {
super(props)
this.state = TestComponent.getStateFromStores()
}
render() {
return (
<div>
<img src='' alt=''/>
</div>
)
}
}
Test:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState') // .and.returnValue(mockTestStoreData)
TestComponent = require('../TestComponent.jsx')
spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
it('should call getStateFromStores on construction', () => {
expect(TestComponent.getStateFromStores).toHaveBeenCalled()
})
it('should call getState on TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled()
})
})
TestStore.getState() is supposed to be called from TestComponent.getStateFromStores(), but you are spying on TestComponent.getStateFromStores():
...
spyOn(TestComponent, 'getStateFromStores');
...
so the actual implementation is not being invoked during your tests. To spy on a function and also have it called, you could change to:
spyOn(TestComponent, 'getStateFromStores').and.callThrough();
Documentation for callThrough().
That being said, testing whether or not methods get called inside the module you are testing is perhaps too implementation specific. It's better to keep the test more cause-and-effect oriented. So my suggested solution would be to make the following adjustments:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState').and.returnValue(mockTestStoreData);
TestComponent = require('../TestComponent.jsx')
// don't spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
// don't
//it('should call getStateFromStores on construction', () => {
// expect(TestComponent.getStateFromStores).toHaveBeenCalled()
//})
// don't
//it('should call getState on TestStore', () => {
// expect(TestStore.getState).toHaveBeenCalled()
//})
// instead
it( 'should get its initial state from TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled(); // I'd say optional
expect( TestComponent.state ).toEqual( mockTestStoreData );
})
})
Now you're free to change the implementation and are testing what is important: the state of TestComponent after it is initialized.