Functional component does not work when importing it in test - javascript

I have a functional component Foo.js that looks like this:
const Foo = () => {
return (
<View></View>
)
}
export default Foo
This component works fine when rendering it in the app.
The issue is when trying to test the component like this:
import renderer from 'react-test-renderer'
import Foo from './Foo'
test('testing', () => {
const component = renderer.create(<Foo />) <--- Error occurs
})
An error occurs when running this test (when calling renderer.create), saying:
Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
The strange thing is that the test passes without any error if I put the component inside the test file:
const Foo = () => {
return (
<View></View>
)
}
test('testing', () => {
const component = renderer.create(<Foo />)
})

Turns out that the cause for this was that the exported component got mocked, due to that I had automock set to true in my jest config.
Changing this in jest.config.js:
automock: true
Into this:
automock: false
Resolves the issue.
Question: Why is the automocked component not returning anything? Could that be solved in some way so that it would work having automock set to true?

Related

FeatureToggle React Jest - TypeError: Cannot read property 'state' of undefined

I've got a component that uses featuretoggle-react to render some stuff. For example:
import { On, FeatureToggle } from "featuretoggle-react";
const MyComponent = () => {
return (
<FeatureToggle feature="my_feature">
<On>
// render whatever
</On>
</FeatureToggle>
)
}
I'm now creating tests using Jest for MyComponent. An example is as follows:
import React from "react";
import { Provider } from "react-redux";
import { On, FeatureToggle } from "featuretoggle-react";
import { store } from "stores";
test("load my component", async () => {
const mockComponent = () => <div></div>;
jest.mock("featuretoggle-react", () => mockComponent);
render(
<Provider store={store}>
<FeatureToggle feature="my_feature"> // error is signaled here
<On>
<MyComponent />
</On>
</FeatureToggle>
</Provider>
);
// assert some stuff but there is an error returning above
});
When I run the above test, I get the resultant error as:
TypeError: Cannot read property 'state' of undefined
If I comment out the <FeatureToggle feature="my_feature"> from within MyComponent (as well as in the test), my tests run successfully, which is why I'm inclined to believe that it is an error thrown because of <FeatureToggle />
I have tried to mock it using
const mockComponent = () => <div></div>;
jest.mock("featuretoggle-react", () => mockComponent);
but I still get the same error.
EDIT: even if I don't mock featuretoggle-react, I still get the same error.

Mock an imported Component that is passed in as a prop

I have a component ExternalComponent which comes from a different module fetched via another
configuration js file. This is then passed in as a prop into the Component I am testing as follows.
const LocalComponent = ({
externalComponent: ExternalComponent, // imported Component coming in as a prop
}) => {
const onChange = (index) => {
console.log('test);
SetData1();
SetData2();
};
return (
<div className={styles.ExternalComponent}>
<ExternalComponent
onChange={onChange}
/>
</div>
);
};
export default LocalComponent;
I am trying to test that the onChange works via following test using mount.
But unable to do so ending up with following error.
Uncaught [Error: mockConstructor(...): Nothing was returned from
render. This usually means a return statement is missing.
shallow works fine but I am looking to do the test via mount as I am checking the onChange.
Is there a way around this? Cos I can't just mock it as follows since this is not a Component inside my project.
// not gonna work, is an imported Component, not in my paths.
jest.doMock('./ExternalComponent', () => {
const ExternalComponent = () => <div />;
return ExternalComponent;
});
This is the test that is failing with above error.
import React from 'react';
import { shallow, mount } from 'enzyme';
import LocalComponent from './LocalComponent';
describe('LocalComponent', () => {
const mockSetData1 = jest.fn();
const mockSetData2 = jest.fn();
const mockExternalComponent = jest.fn();
const defaultProps = {
externalComponent: mockExternalComponent,
SetData1: mockSetData1,
SetData2: mockSetData2
};
const shallowRender = props => shallow(<LocalComponent {...defaultProps} {...props} />);
const mountRender = props => mount(<LocalComponent {...defaultProps} {...props} />);
// works fine since using shallow
it('should render', () => {
const rendered = shallowRender();
expect(rendered).toMatchSnapshot();
});
// FAILING TEST
it('should render via mount too', () => {
const rendered = mountRender(); // this breaks with above error
// trying to achieve following test, currently unavailable.
rendered.find('.ExternalComponent').simulate('click');
expect(mockSetData1).toHaveBeenCalled();
expect(mockSetData2).toHaveBeenCalled();
});
});
In the ExternalComponent props, it seems that your function onChange is triggered when External's function onChange is triggered. I assume that the onChange function in the ExternalComponent is linked to an input, with a value prop, and that the methods SetData1 and SetData2 come from props. (Aren't they missing in your LocalComponent props ?)
To trigger it, in your test, you should simulate the "change" action rather than the "click" action in your ExternalComponent.
Updating this, you should have the following:
it('should render via mount too', () => {
const wrapper = mount(<LocalComponent {...defaultProps} />
wrapper.find('.ExternalComponent').simulate('change', { target: { value: "newValue" } });
expect(mockSetData1).toHaveBeenCalled();
expect(mockSetData2).toHaveBeenCalled();
});

Is there a way to unit test an innaccessible callback function that is called from a child React component using jest

I am unit testing a React 16 functional component that defines a callback and passes it as a property down to the next component to be invoked when a click event fires.
import React from 'react'
function OuterComponent() {
const handleClick = useCallback(() => {
openDialog() // <--- This code is inaccessible from my "OuterComponent.test.js" unit test
}
return <MyDialog clickHandler={handleClick} />
}
export default OuterComponent
I am seeking 100% test coverage and would like to mock all other components/functions using jest that are not part of the "OuterComponent" component.
The problem I am having is that I can't seem to mock the MyDialog component in order to have it trigger the handleClick function.
I also tried to mock the component using this snippet but it still seems to try to load all the imports from MyDialog
jest.doMock('components/MyDialog', () => {
return () => () => <div />
})
//... other mocks along with **openDialog**
//...and in my describe/it...
MyDialog.mockImplementation(() => {})
OuterComponent().handleClick()
expect(openDialog).toHaveBeenCalled()
The hope here was that I could render an empty div for MyDialog and just simply call the handleClick function to test it. Does anyone know what I could be doing wrong or have any other ideas of how to test it?
I don't think you can mock a var in a closure. The best you can do is something along those lines:
import { fireEvent, render } from 'react-testing-library';
jest.mock(
'components/MyDialog',
() => ({ clickHandler }) => (
<button
onClick={clickHandler}
test-id="button"
>
Click me
</button>
),
);
it('should call clickHandler', () => {
const onClick = jest.fn(() => null);
const { queryByTestId } = render(<MyDialog clickHandler={onClick} />);
fireEvent.click(queryByTestId('button'));
expect(onClick.toHaveBeenCalledTimes(1));
});
You actually test the ability for you MyDialog to render something that can execute a callback.

Trying to call setState of main app.js throws component is not mounted warning

I've had major issues the last few days getting state information into the custom contentComponent of my createDrawerNavigator. I've decided to make a few pieces of content global such as user ID using the state of app.js and just passing the state as screenProps to the router like so.
App.js
state = {
score:0,
rank:0,
}
setScore = (value) => this.setState({score:value});
setRank = (value) => this.setState({rank:value});
render() {
const globalProps={
state:this.state,
setScore:this.setScore,
setRank:this.setRank
}
let RootNav = createRootNavigator();
return (
<RootNav screenProps={globalProps}></RootNav>
);
Router.js
contentComponent: ({ navigation, screenProps}) => (
<DrawerContent navigation={navigation} screenProps={screenProps}/>
),
Child.js
this.props.screenProps.setScore(5);
I'm able to access the data, but when I call to setState from the child I get the warning telling me that app.js is unmounted. My understanding was that app.js was always mounted and running because it contains your entire app? if anyone has a solution for this it'd be greatly appreciated.
I think you're getting this error because you're setting your state in the render method of your component. You would need to move it up into a lifecycle method like componentDidMount(). See the example below of your refactored code.
state = {
score:0,
rank:0,
}
setScore = (value) => this.setState({score:value});
setRank = (value) => this.setState({rank:value});
componentDidMount(){
const globalProps={
state:this.state,
setScore:this.setScore,
setRank:this.setRank
}
}
render() {
let RootNav = createRootNavigator();
return (
<RootNav screenProps={globalProps}></RootNav>
);
The componentDidMount() runs everytime the component is called or data/state changes. I think setting your state in the render() is not good practice. i havent actually tested this code but if it doesnt work you could try sending more code snippets.

React: How mock functions and test component rendering with jest:

Im very new to react/jest. Im trying to test a very simple react component that gets data from the server and renders the response. My component looks like the below:
export default class myComponent extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
try {
let response = await axios.get(`server/url/endpoint`);
this._processSuccess(response.data);
} catch(e) {
this._processFail(e);
}
}
_processSuccess(response) {
this.setState({pageTitle: response.data.title, text: response.data.text});
}
render() {
return (
<div className="title">{this.state.pageTitle}</div>
);
}
}
Now I want to test this class. While I test:
I want to make sure componentDidMount() was not called
I want to pass test data to _processSuccess
Finally check the if the rendered output contains a div with class title that has the inner text same as what I supplied as response.data/pageTitle
I tried something like the below:
import React from 'react'
import MyComponent from './MyComponent'
import renderer from 'react-test-renderer'
import { shallow, mount } from 'enzyme'
describe('MyComponent', () => {
it('should display proper title', () => {
const c = shallow(<MyComponent />);
c._processSuccess(
{data:{pageTitle:'siteName', test:'text'}}
);
// couldn't go further as Im getting error from the above line
});
});
But, Im getting MyComponent._processSuccess is not a function error. What would be the proper way to do that.
shallow() returns an Enzyme wrapper with some utils method to test the rendered component. It does not return the component instance. That's why you get the error when calling c._processSucces(). To access the component you can use the .instance() method on the wrapper, so the following should work:
const c = shallow(<MyComponent />);
c.instance()._processSuccess(
{data:{pageTitle:'siteName', test:'text'}}
);
In order to avoid that component's componentDidMount() get called, you can try settings disableLifecycleMethods on the shallow renderer, but I'm not sure about that because here Enzyme's documentation is not 100% clear:
const c = shallow(<MyComponent />, {
disableLifecycleMethods: true
});
Finally, you can check if the output contains the expected <div>, using Enzyme's contains() and one of Jest assertion methods:
expect(c.contains(<div className="title" />)).toBe(true);

Categories