jest/enzyme test: this.props.showOverlay is not a function - javascript

I have a small test that simulates a click (hoping to do more with the test, but this is where I'm stuck so far):
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import HamburgerIcon from './HamburgerIcon';
Enzyme.configure({ adapter: new Adapter() });
test('hamburger icon changes class and state on click', () => {
const wrapper = mount(<HamburgerIcon />);
const hamburgerIcon = wrapper.find('div#mobile-nav');
hamburgerIcon.simulate('click');
});
When running this test, I get the error:
TypeError: this.props.showOverlay is not a function
After doing some reading, I've realized that this isn't working because the simulated click calls a function that is two levels up from the component that is being tested (HamburgerIcon).
When I first tried to run this, I was using Enzyme's shallow, and I since changed it to mount thinking that this would give the test access to the showOverlay function, but I was wrong.
Then I read that this might be a good use case for a mock function, and I tried to start implementing this:
...
const showOverlay = jest.fn();
// how to set this.props.ShowOverlay to the const above??
test('has div with class .closed', () => {
const wrapper = mount(<HamburgerIcon />);
const hamburgerIcon = wrapper.find('div#mobile-nav');
hamburgerIcon.simulate('click');
});
This is where I am sort of lost -- I'm not sure if mock functions are the right direction here, and I'm also not sure how the syntax of setting up the mock function will work.

Continue with shallow if you're just unit testing a single component. If this component is nested and you're testing against child nodes, then you'll mount the parent.
That said, you're on the right track for using a mock function. Simply pass it into the component like so:
<HamburgerIcon showOverlay={showOverlay} />
For example:
const showOverlay = jest.fn();
test('shows an overlay', () => {
const wrapper = mount(<HamburgerIcon showOverlay={showOverlay} />);
const hamburgerIcon = wrapper.find('div#mobile-nav');
hamburgerIcon.simulate('click');
expect(showOverlay).toHaveBeenCalled();
});
If you have multiple props, then I like to do something more declarative:
// define props here if you want an easy way check if they've been
// called (since we're defining at the top-level, all tests have access
// to this function)
const showOverlay = jest.fn();
// then include them in an "initialProps" object (you can also just define
// them within this object as well, but you'll have to use
// "initialProps.nameOfFunction" to check if they're called -- kind of
// repetitive if you have a lot of props that you're checking against)
const initialProps = {
showOverlay,
someOtherFunction: jest.fn()
}
// use the spread syntax to pass all the props in the "initialProps" object
// to the component
test('shows an overlay', () => {
const wrapper = mount(<HamburgerIcon { ...initialProps } />);
const hamburgerIcon = wrapper.find('div#mobile-nav');
hamburgerIcon.simulate('click');
expect(showOverlay).toHaveBeenCalled(); // here we can just use the mock function name
expect(initialProps.someOtherFunction).toHaveBeenCalledTimes(0); // here we'll have to use "initialProps.property" because it has been scoped to the object
});

Related

React HOC: Pass data attributes to the first child/element of wrapped component

I have a hoc component like this:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => (
<WrappedComponent {...props} data-test-id="this-is-a-element" />
);
return ModifiedComponent;
};
export default withAttrs;
and I use it like this:
import React from 'react';
import withAttrs from './withAttrs';
const SomeLink = () => <a><p>hey</p</a>;
export default withAttrs(SomeLink);
I expect to have an anchor tag like this:
<a data-test-id="this-is-a-element"><p>hey</p></a>
But the hoc doesn't add the data-attribute to the first element. Is there a way to achieve this?
But the hoc doesn't add the data-attribute to the first element.
It's not the HOC that isn't adding it, it's SomeLink, which doesn't do anything with the props the HOC passes to it.
The simple answer is to update SomeLink:
const SomeLink = (props) => <a {...props}><p>hey</p></a>;
That's by far the better thing to do than the following.
If you can't do that, you could make your HOC add the property after the fact, but it seems inappropriate to have the HOC reach inside the component and change things. In fact, React makes the element objects it creates immutable, which strongly suggests you shouldn't try to mess with them.
Still, it's possible, it's probably just a bad idea:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
/*export*/ const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
// THIS IS PROBABLY A VERY BAD IDEA. React makes these objects
// immutable, probably for a reason. We shouldn't be mucking
// with them.
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
const SomeLink = () => <a><p>hey</p></a>;
const SomeLinkWrapped = withAttrs(SomeLink);
const Example = () => {
return <div>
<div>Unwrapped:</div>
<SomeLink />
<div>Wrapped:</div>
<SomeLinkWrapped />
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
/* So we can see that it was applied */
[data-test-id=this-is-a-element] {
color: green;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Again, I don't think I'd do that except as a very last resort, and I wouldn't be surprised if it breaks in future versions of React.

Jest Mock React Component

I'm using a plugin that renders out a form using json schema. For elements like input, button, etc, it uses a React component within the structure to render out the component. In our app, we receive schema json that describes the layout. For example, we could receive something like this (simplified to make it easier to read)
{
component: 'input'
}
and I have a convertor function that places the component in where one is detected in the structure. It will send something do like: (again, simplified)
import Table from './Table';
covert(schema) {
return {
component: Table // where table is: (props:any) => JSX.Element
}
}
I want to write a test for this, but having trouble with the comparing the result with the expected. In my test, I mock the Table component and send through a named mock function as the second param. Then I use the same named param in the expected results.
I get an error back: The second argument ofjest.mockmust be an inline function. I can change this to an inline function, but then how can I use this in the expected structure used for comparison?
// Test code
import React from 'react';
const mockComponent = () => <div>table</div>
jest.mock('./Table', mockComponent);
const schema = {
component: 'table'
}
describe('Component Structure', () => {
test('componentizes the schema structure', () => {
const results = convert(schema);
const expected = {
component: mockComponent
};
expect(results).toEqual(expected);
});
});
The error is because you are not mocking the component properly, the right way should be:
jest.mock('./Table', () => mockComponent);
given that you already have mockComponent defined as:
const mockComponent = () => <div>table</div>
or you could do:
jest.mock('./Table', () => () => <div />);
The proper mocking of the components would be something like this:
const mockComponent = () => <div>table</div>
jest.mock('./Table', () => mockComponent)

Where to mount and unmount inside an Enzyme describe function?

I'm trying to get the hang of testing components in a React project. I have a single test file on a single component so far, and I'm trying to prepare this file as a test suite with multiple tests in it.
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import HamburgerIcon from './HamburgerIcon';
Enzyme.configure({ adapter: new Adapter() });
describe('<HamburgerIcon />', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
it('displays on mobile', () => {
...
...
});
it('has class .open after click', () => {
...
...
});
hamburgerIcon.unmount();
});
I've removed the guts of the two tests, but basically the two tests are wrapped inside of a describe function, and I'm trying to mount the component once and unmount the component once in an effort to keep things DRY (don't repeat yourself).
I've placed the mount before the two it functions, thinking that mounting the component before running tests makes logical sense.
I placed the unmount after the two test functions, which causes the error:
Method “simulate” is meant to be run on 1 node. 0 found instead.
I think this is happening because the component is unmounting before the tests are actually run.
If I mount and unmount in both tests, like this...
describe('<HamburgerIcon />', () => {
it('displays on mobile', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
...
...
hamburgerIcon.unmount();
});
it('has class .open after click', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
...
...
hamburgerIcon.unmount();
});
});
...the tests pass.
This seems excessive, though. What if my test suite has ten test functions it? Should I be mounting and unmounting like this for every single test?
You can use beforeEach and afterEach functions to set-up and clear your test.
afterEach(() => {
//do the unmounting and other stuff here
//this will be called after each test case
});
beforeEach(() => {
//do the mounting and setting up the test case here
//this will be called before each test case
});

Using Jest and Enzyme, how do I test a function passed in through props?

Using Jest and Enzyme, how can I test if this.props.functionToTest was run?
class TestComponent extends Component {
static propTypes = {
functionToTest: PropTypes.func
}
componentDidMount() {
this.props.functionToTest()
}
}
In Jest, I've tried creating mockProps and passing them in when mounting the component.
let props = {
functionToTest = jest.fn(() => {});
}
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
A console.log in the componentDidMount function shows functionToTest as undefined. Obviously passing in the props during mount isn't working.
Question 1: How can I pass in mock props that will show in the componentDidMount function?
Question 2: Once that function is available, how do I gain access to the function so I can use spyOn or something similar to test if the function was run?
I don't know your exact setup, but this is how I would do that:
Mock the function with jest.fn() like you did
Pass mock to the component being mounted (like apparently you did)
Check whether it was run with expect(...).toBeCalled() or .toHaveBeenCalled() (varies between different Jest versions)
.
let props = {
functionToTest: jest.fn() // You don't need to define the implementation if it's empty
};
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
// In the test code:
it('does something', () => {
expect(props.functionToTest).toBeCalled();
// OR... depending on your version of Jest
expect(props.functionToTest).toHaveBeenCalled();
});
The problem ended up being that TestComponent was only being exported within the Redux wrapper. Adding an export at the class level and destructuring it in the Jest test import, along with the solution Henrick posted above fixed it.

Write a jest test to my first component

I just finished writing my first Reactjs component and I am ready to write some tests (I used material-ui's Table and Toggle).
I read about jest and enzyme but I feel that I am still missing something.
My component looks like this (simplified):
export default class MyComponent extends Component {
constructor() {
super()
this.state = {
data: []
}
// bind methods to this
}
componentDidMount() {
this.initializeData()
}
initializeData() {
// fetch data from server and setStates
}
foo() {
// manuipulatig data
}
render() {
reutrn (
<Toggle
id="my-toggle"
...
onToggle={this.foo}
>
</Toggle>
<MyTable
id="my-table"
data={this.state.data}
...
>
</MyTable>
)
}
}
Now for the test. I want to write a test for the following scenario:
Feed initializeData with mocked data.
Toggle my-toggle
Assert data has changed (Should I assert data itself or it is better practice to assert my-table instead?)
So I started in the very beginning with:
describe('myTestCase', () => {
it('myFirstTest', () => {
const wrapper = shallow(<MyComponent/>);
}
})
I ran it, but it failed: ReferenceError: fetch is not defined
My first question is then, how do I mock initializeData to overcome the need of calling the real code that using fetch?
I followed this answer: https://stackoverflow.com/a/48082419/2022010 and came up with the following:
describe('myTestCase', () => {
it('myFirstTest', () => {
const spy = jest.spyOn(MyComponent.prototype, 'initializeData'
const wrapper = mount(<MyComponent/>);
}
})
But I am still getting the same error (I also tried it with componentDidMount instead of initializeData but it ended up the same).
Update: I was wrong. I do get a fetch is not defined error but this time it is coming from the Table component (which is a wrap for material-ui's Table). Now that I come to think about it I do have a lot of "fetches" along the way... I wonder how to take care of them then.
fetch is supported in the browser, but jest/enzyme run in a Node environment, so fetch isn't a globally available function in your test code. There are a few ways you can get around this:
1: Globally mock fetch - this is probably the simplest solution, but maybe not the cleanest.
global.fetch = jest.fn().mockResolvedValue({
json: () => /*Fake test data*/
// or mock a response with `.text()` etc if that's what
// your initializeData function uses
});
2: Abstract your fetch call into a service layer and inject that as a dependency - This will make your code more flexible (more boilerplate though), since you can hide fetch implementation behind whatever interface you choose. Then at any point in the future, if you decide to use a different fetch library, you can swap out the implementation in your service layer.
// fetchService.js
export const fetchData = (url) => {
// Simplified example, only takes 'url', doesn't
// handle errors or other params.
return fetch(url).then(res => res.json());
}
// MyComponent.js
import {fetchService} from './fetchService.js'
class MyComponent extends React.Component {
static defaultProps = {
// Pass in the imported fetchService by default. This
// way you won't have to pass it in manually in production
// but you have the option to pass it in for your tests
fetchService
}
...
initializeData() {
// Use the fetchService from props
this.props.fetchService.fetchData('some/url').then(data => {
this.setState({ data });
})
}
}
// MyComponent.jest.js
it('myFirstTest', () => {
const fetchData = jest.fn().mockResolvedValue(/*Fake test data*/);
const fetchService = { fetchData };
const wrapper = mount(<MyComponent fetchService={fetchService} />);
return Promise.resolve().then(() = {
// The mock fetch will be resolved by this point, so you can make
// expectations about your component post-initialization here
})
}

Categories

Resources