I'm attempting to flesh put my understanding of Jest testing as some of my components are lacking subsequent testing. Consider this:
import React, { ReactElement, useEffect, useState } from "react";
import { IPlaylist } from "src/interfaces/playlist";
import usePlaylists from "src/providers/playlists/hooks/use-playlists";
const JestTesting = (): ReactElement => {
const { playlists, addPlaylist } = usePlaylists();
const examplePlaylist = {
id: `${Math.floor(Math.random() * 1000)}`,
name: "testing",
created: new Date().toISOString(),
createdBy: "Biggus Dickus",
updated: new Date().toISOString(),
version: 0,
tracks: []
};
const createPlaylist = () =>
addPlaylist(examplePlaylist);
useEffect(() => {
if (playlists.length > 0) {
console.log("playlists updated")
}
}, [playlists]);
return (
<div>
<h2>Jest Testing</h2>
{playlists.map((p) => <h3 key={p.id}>{p.name}</h3>)}
<button onClick={ () => createPlaylist() }>
Create New Playlist
</button>
</div>
);
};
export default JestTesting;
This is just a very simple component that leverages the custom provider I made; the provider (in this component) has an initial playlist value of an empty array and a function for updating that array.
Here's the test:
import React from "react";
import { render, RenderResult, fireEvent, screen } from "#testing-library/react";
import JestTesting from "..";
const mockPlaylists = jest.fn().mockReturnValue([]);
const mockAddPlaylist = jest.fn();
// First mock provider attempt
jest.mock("src/providers/playlists/hooks/use-playlists", () => () => {
return {
playlists: mockPlaylists(),
addPlaylist: mockAddPlaylist
};
});
const clickCreatNewPlaylistBtn = () =>
fireEvent.click(screen.getByText("Create New Playlist"));
describe("JestTesting", () => {
let rendered: RenderResult;
const renderComponent = () => render(<JestTesting />);
beforeEach(() => {
rendered = renderComponent();
});
it("renders the component", () => {
expect(rendered.container).toMatchSnapshot();
});
describe("when new playlist is created", () => {
it("updates view/snapshot", () => {
clickCreatNewPlaylistBtn();
expect(mockAddPlaylist).toHaveBeenCalled();
expect(rendered.container).toMatchSnapshot();
});
});
});
which spits out this snapshot:
// Jest Snapshot v1,
exports[`JestTesting renders the component 1`] = `
<div>
<div>
<h2>
Jest Testing
</h2>
<button>
Create New Playlist
</button>
</div>
</div>
`;
exports[`JestTesting when new playlist is created updates view/snapshot 1`] = `
<div>
<div>
<h2>
Jest Testing
</h2>
<button>
Create New Playlist
</button>
</div>
</div>
`;
The tests pass and I can see that the mockAddPlaylist function is called, but the problem is that the playlists array in the snapshot is never updated and the snapshots don't change between the first and the second test.
What am I doing wrong here? Do I need to wait for the effects of the addPlaylist function to finish? If so, what's the best way to do?
Thanks!
Related
Clunking through learning testing with jest + enzyme. I have an array, OptionsArray, with some options that get mapped to buttons in a component. I figured that in the testing suite for the component, I could just do
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { OptionsArray } from './ConfigOptions';
import Foo from './Foo';
describe('Foo', () => {
let wrapper: ShallowWrapper;
const numberOfOptions = OptionsArray.length;
beforeEach(() => (wrapper = shallow(<Foo />)));
it('renders exactly one Button Item for each option', () => {
/* eslint-disable-next-line testing-library/no-debugging-utils */
console.log(wrapper.debug());
OptionsArray.forEach((option) => {
console.log(option.value);
});
OptionsArray.forEach((option) => {
expect(wrapper.find(option.value)).toHaveLength(1);
});
});
});
I see the options fine in the console output, but then I get:
Foo › renders exactly one Button Item for each option
expect(received).toHaveLength(expected)
Expected length: 1
Received length: 0
So I'm guessing that I'm passing the variable to find incorrectly? Is there a better way to do this?
Adding component Foo:
/* Foo.tsx */
import React, { useState } from 'react';
import { Button, ListGroup } from 'react-bootstrap';
import { OptionsArray } from './ConfigOptions';
import './Foo.scss';
const Foo: React.FC<> = () => {
const [options, setOptions] = useState(OptionsArray);
return (
<div className="Foo">
<ListGroup>
{OptionsArray.map((option, i) => (
<ListGroup.Item key={i}>
<Button
id={i.toString()}
value={option.value}
onClick={(e) => handleClick(e.currentTarget.id)}
variant={option.isSet ? 'primary' : 'outline-primary'}
>
{option.value}
</Button>
{option.content}
</ListGroup.Item>
))}
</ListGroup>
</div>
);
};
export default Foo;
And the OptionsArray:
import React from 'react';
export const OptionsArray = [
{
value: 'OptionA',
content: (
<React.Fragment>
<br />
<p>Here is a description of OptionA.</p>
</React.Fragment>
),
isSet: false,
},
{
value: 'OptionB',
content: (
<React.Fragment>
<br />
<p>Here is a description of OptionB.</p>
</React.Fragment>
),
isSet: false,
},
];
I figured it out. As usual, just a misunderstanding on my part. I was trying to use find to get the Button components by text, but this isn't how find works. Instead, I needed to use the findWhere method and a predicate to hunt down the exact components I was looking for. Here was my solution:
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { OptionsArray } from './ConfigOptions';
import Foo from './Foo';
describe('Foo', () => {
let wrapper: ShallowWrapper;
const numberOfOptions = OptionsArray.length;
beforeEach(() => (wrapper = shallow(<Foo />)));
it('renders exactly one Button Item for each option', () => {
OptionsArray.forEach((option) => {
expect(wrapper.find({ value: option.value })).toHaveLength(1);
const ButtonWithText = wrapper.findWhere((node) => {
return node.name() === 'Button' && node.text() === option.value;
});
expect(ButtonWithText ).toHaveLength(1);
});
});
});
I'm pretty new to Jest and testing, so I'm making an app using React, React Testing Library, and Jest to improve my skills.
One of my tests is failing, and I can't figure out why. Here is the code from my test:
import { render, screen, waitFor } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// using UrlShortener since the state has been lifted up for UrlList
import UrlShortener from '../../pages/UrlShortener/UrlShortener'
...
test('URL list displays valid URL from input bar', async () => {
const passingText = 'http://www.google.com';
const testText = 'test4';
render(<UrlShortener />);
const urlInput = screen.getByPlaceholderText('Enter URL here...');
const nameInput = screen.getByPlaceholderText('Name your URL...');
const submitBtn = screen.getByRole('button', { name: 'Shorten!' });
userEvent.type(urlInput, passingText);
userEvent.type(nameInput, testText);
userEvent.click(submitBtn);
const listButton = screen.getByText('Link History');
userEvent.click(listButton);
const list = await screen.findAllByText(/visits/i);
await waitFor(() => expect(list).toHaveLength(4));
});
The thing that's confusing me is that I can see that the list is 4 elements long in the log from the failing test, but for some reason it's not getting picked up in the expect() function. Here's what the log is giving me (it clearly shows 4 elements in the list):
expect(received).toHaveLength(expected)
Expected length: 4
Received length: 3
Received array: [<p>Visits: 2</p>, <p>Visits: 1</p>, <p>Visits: 5</p>]
...
<div>
<div
class="sc-iqHYmW gBcZyO"
>
<p>
<a
href="http://www.baseUrl.com/123"
>
test1
</a>
</p>
<p>
Visits:
2
</p>
</div>
<div
class="sc-iqHYmW gBcZyO"
>
<p>
<a
href="http://www.baseUrl.com/456"
>
test2
</a>
</p>
<p>
Visits:
1
</p>
</div>
<div
class="sc-iqHYmW gBcZyO"
>
<p>
<a
href="http://www.baseUrl.com/789"
>
test3
</a>
</p>
<p>
Visits:
5
</p>
</div>
<div
class="sc-iqHYmW gBcZyO"
>
<p>
<a
href="http://www.baseUrl.com/shorten/123"
>
test4
</a>
</p>
<p>
Visits:
9
</p>
</div>
</div>
How is it possible that the DOM is behaving as expected in the log, but is failing in the actual test?
Update:
I'm adding more information so it's obvious what I'm doing. Basically, I've lifted state up from a child component (UrlList) to the parent (UrlShortener) so that I could pass a state updater function down to a sibling (UrlBar). The UrlShortener makes an axios call to the backend, then passes down a list of URLs to the UrlList component. When you click the submit button in the UrlBar component, it re-runs the axios call and updates the list with the new URL added.
Parent component:
import { useEffect, useState } from 'react';
import { SectionPage, BackButton, PageTitle } from './style';
import axios from 'axios';
import UrlBar from '../../components/UrlBar/UrlBar';
import UrlList from '../../components/UrlList/UrlList';
import { Url } from '../../types/types';
const UrlShortener = () => {
const [urls, setUrls] = useState<Url[] | []>([]);
const getUrls = () => {
axios
.get('https://fullstack-demos.herokuapp.com/shorten/urls/all')
.then((res) => setUrls(res.data));
};
useEffect(() => {
getUrls();
}, []);
return (
<SectionPage>
<BackButton href='/'>Go Back</BackButton>
<PageTitle>URL Shortener</PageTitle>
<UrlBar getUrls={getUrls} />
<UrlList urls={urls} />
</SectionPage>
);
};
export default UrlShortener;
Children:
import React, { useState } from 'react';
import {
ComponentWrapper,
Subtitle,
Triangle,
LinksContainer,
LinkGroup,
} from './style';
import { Url } from '../../types/types';
interface IProps {
urls: Url[] | [];
}
const UrlList: React.FC<IProps> = ({ urls }) => {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prevState) => !prevState);
};
return (
<ComponentWrapper>
<Subtitle onClick={handleClick}>
Link History <Triangle>{open ? '▼' : '▲'}</Triangle>
</Subtitle>
<LinksContainer>
<div>
{open &&
urls.map(({ urlId, shortUrl, urlName, visits }: Url) => (
<LinkGroup key={urlId}>
<p>
<a href={shortUrl}>{urlName}</a>
</p>
<p>Visits: {visits}</p>
</LinkGroup>
))}
</div>
</LinksContainer>
</ComponentWrapper>
);
};
export default UrlList;
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { UrlInput, NameInput, UrlButton } from './style';
import { validateUrl } from '../../utils/utils';
interface IProps {
getUrls: () => void;
}
const UrlBar: React.FC<IProps> = ({ getUrls }) => {
const [urlInput, setUrlInput] = useState('');
const [nameInput, setNameInput] = useState('');
const [error, setError] = useState<boolean | string>(false);
useEffect(() => {
// Cleanup fixes React testing error: "Can't perform a React state update on an unmounted component"
return () => {
setUrlInput('');
};
}, []);
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUrlInput(e.target.value);
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNameInput(e.target.value);
};
const handleSubmit = async (e: React.SyntheticEvent) => {
e.preventDefault();
if (!nameInput) {
setError('Please name your URL');
} else if (!validateUrl(urlInput)) {
setError('Invalid Input');
} else {
setError(false);
await axios.post('https://fullstack-demos.herokuapp.com/shorten', {
longUrl: urlInput,
urlName: nameInput,
});
setUrlInput('');
setNameInput('');
getUrls();
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<NameInput
type='text'
name='nameInput'
id='nameInput'
placeholder='Name your URL...'
maxLength={20}
onChange={handleNameChange}
value={nameInput}
/>
<UrlInput
type='text'
name='urlInput'
id='urlInput'
placeholder='Enter URL here...'
onChange={handleUrlChange}
value={urlInput}
/>
<UrlButton name='button' type='submit'>
Shorten!
</UrlButton>
{error && <label htmlFor='urlInput'>{error}</label>}
</form>
</div>
);
};
export default UrlBar;
So after fighting to get my tests to pass for another component, I finally figured out how to get this one to pass. Apparently I just needed to add a few more waitFor() and await statements to catch some of the async stuff happening in my component. I'd be lying if I said I understand why this fixes my problem, but now I know that if my tests are failing even though I can see the right results in the JEST DOM, it probably has to do with missing waitFor / awaits.
test('URL list displays valid URL from input bar', async () => {
const passingText = 'http://www.google.com';
const testText = 'test4';
render(<UrlShortener />);
const urlInput = screen.getByPlaceholderText('Enter URL here...');
const nameInput = screen.getByPlaceholderText('Name your URL...');
const submitBtn = screen.getByRole('button', { name: 'Shorten!' });
userEvent.type(urlInput, passingText);
userEvent.type(nameInput, testText);
await waitFor(() => userEvent.click(submitBtn));
const listButton = await screen.findByText('Link History');
await waitFor(() => userEvent.click(listButton));
const list = await screen.findAllByText(/visits/i);
await waitFor(() => expect(list).toHaveLength(4));
});
});
I'm trying to learn test-driven development, so I have created a simple training app composed of 2 simple working components App.js which saves an array of data coming from a dummy local JSON file to the App.js component state then map through it and render a User component with props for each element of the data array.
So I have 3 uncovered lines which all contain "if statements" and, I want to achieve 100% test coverage on them, so please help.
Here are the results of the tests.
these are the uncovered lines for App.js from line 18 and 32 are the ternary expressions in each function
clickFollowHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isFollowed === 'active' ? user.isFollowed = 'idle' : user.isFollowed = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
clickStarHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isStared === 'active' ? user.isStared = 'idle' : user.isStared = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
and this is User.js line 23 is the checkbox ternary expression
return(
<div className={classes.container} key={id}>
<img className={classes.imageContainer} src={myImage} alt={name} />
<div className={classes.contentContainer}>
<div className={classes.contentContainerRow1}>
<div className={classes.name}>name: {name}</div>
<button onClick={() => handleFollowClick(id)}>
{isFollowed === 'active' ? 'Unfollow' : 'Follow'}
</button>
</div>
<div className={classes.contentContainerRow2}>
<div className={classes.date}>date: {date}</div>
<div className={classes.time}>reading time: {readingTime}</div>
<input
className={classes.hvrIconPop}
checked={isStared === 'active' ? true : false}
onChange={() => handleStarClick(id)}
type='checkbox'
/>
</div>
</div>
</div>
)
Now this is my App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import {shallow, mount} from './enzyme';
import App from './App';
jest.mock('./data/users-data.json')
let {user} = require('./data/users-data.json')
describe('App Component', () => {
it('calling the clickFollowHandler method from App Component has the expected effect on the state of the first user', () => {
const AppComponent = shallow(<App />)
const wrapper = AppComponent.instance()
wrapper.clickFollowHandler('5d552d0058f193f2795fc814')
expect(wrapper.state.users[0].isFollowed).toMatch('idle')
})
})
describe('App Component', () => {
it('calling the clickStarHandler method from App Component has the expected effect on the state of the second user', () => {
const AppComponent = shallow(<App />)
const wrapper = AppComponent.instance()
wrapper.clickStarHandler('5d552d00b20b141dff10d2a2')
expect(wrapper.state.users[1].isStared).toEqual('idle')
})
})
and my User.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import {shallow, mount} from '../../enzyme';
import User from './User';
const users = {
"id": "5d552d0058f193f2795fc814",
"isFollowed": "active",
"isStared": "idle",
"image": "./assets/images/avata.png",
"readingTime": 20,
"name": "Walton Morton",
"date": "Aug 9"
};
it('renders correctly when there are no users', () => {
const tree = renderer.create(<User
key={''}
id={''}
name={''}
date={''}
readingTime={''}
isStared={''}
isFollowed={''}
image={''}
handleFollowClick={() => {}}
handleStarClick={() => {}}
/>).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders correctly when there is one user', () => {
const tree = renderer.create(<User
key={users.id}
id={users.id}
name={users.name}
date={users.date}
readingTime={users.readingTime}
isStared={users.isStared}
isFollowed={users.isFollowed}
image={users.image}
handleFollowClick={() => 'test'}
handleStarClick={() => {}}
/>).toJSON();
expect(tree).toMatchSnapshot();
});
it('when the follow button is clicked a callback is executed', () => {
const mockFollowClick = jest.fn();
const mockStarClick = jest.fn();
const tree = renderer.create(<User
key={users.id}
id={users.id}
name={users.name}
date={users.date}
readingTime={users.readingTime}
isStared={users.isStared}
isFollowed={users.isFollowed}
image={users.image}
handleFollowClick={mockFollowClick}
handleStarClick={mockStarClick}
/>)
const button = tree.root.findByType('button');
const input = tree.root.findByType('input');
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalled();
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalledWith('5d552d0058f193f2795fc814');
input.props.onChange();
expect(mockStarClick).toHaveBeenCalled();
})
describe('User Component', () => {
it('clicking on the button will trigger the click handler', () => {
const mockFollowHandler = jest.fn();
const mockStarHandler = jest.fn();
const wrapper = mount(<User
key={users.id}
id={users.id}
name={users.name}
date={users.date}
readingTime={users.readingTime}
isStared={users.isStared}
isFollowed={users.isFollowed}
image={users.image}
handleFollowClick={mockFollowHandler}
handleStarClick={mockStarHandler}
/>)
wrapper.find('button').simulate('click');
expect(mockFollowHandler).toHaveBeenCalledWith('5d552d0058f193f2795fc814')
})
it('changing the star checkbox will trigger an onChange handler', () => {
const mockFollowHandler = jest.fn();
const mockStarHandler = jest.fn();
const wrapper = mount(<User
key={users.id}
id={users.id}
name={users.name}
date={users.date}
readingTime={users.readingTime}
isStared={users.isStared}
isFollowed={users.isFollowed}
image={users.image}
handleFollowClick={mockFollowHandler}
handleStarClick={mockStarHandler}
/>)
wrapper.find('input').simulate('change');
expect(mockStarHandler).toHaveBeenCalledWith('5d552d0058f193f2795fc814');
})
});
You need to test against both conditions of the ternary expression (not just one). Since I can't see the complete code, I'll try to offer some brief notes:
For clickStarHandler and clickFollowHandler, you'll need to test against users state and manually invoke the class fields (as you've done). However, you'll need to satisfy the condition when the isFollowed/isStarred matches active and when it matches idle. Fairly straight forward process, however, if you need to update the users state to include some data, then you can simply use wrapper.setState({ users: userData });.
For testing the App:
import React from 'react";
import { mount } from "enzyme";
import App from "../App";
// for simplicity, I'd recommend only using one user
const usersData = [
{
id: "5d552d0058f193f2795fc814",
isFollowed: "active",
isStarred: "idle",
image: "./assets/images/avatar.png",
readingTime: 20,
name: "Walton Morton",
date: "Aug 9"
}
];
// *optional* include any props that are needed for the App
const initialProps = {};
describe("App Component", () => {
let wrapper;
beforeEach(() => (
wrapper = mount(<App { ...initialProps }/>);
wrapper.setState({ users: usersData }); // not needed if user data is already defined in state
)}
it("sets the 'user.isStarred' state to 'active' or 'idle'", () => (
const invokeStarHandler = () => {
wrapper.instance().clickStarHandler("5d552d0058f193f2795fc814");
wrapper.update();
};
invokeStarHandler();
expect(wrapper.state("users[0].isStarred").toEqual("active");
invokeStarHandler();
expect(wrapper.state("users[0].isStarred").toEqual("idle");
});
it("sets the 'user.isFollowed' state to 'active' or 'idle'", () => (
const invokeFollowHandler = () => {
wrapper.instance().clickFollowHandler("5d552d0058f193f2795fc814");
wrapper.update();
};
invokeFollowHandler();
expect(wrapper.state("users[0].isFollowed").toEqual("idle");
invokeFollowHandler();
expect(wrapper.state("users[0].isFollowed").toEqual("active");
});
...etc.
});
For Users testing, simply manipulate the user props; for example, changing wrapper.setProps({ isStarred: "active" }) or wrapper.setProps({ isStarred: "idle" }), then searching for the input and testing against its props:
import React from 'react";
import { mount } from "enzyme";
import Users from "../Users";
// include any props that are needed for the Users component
const initialProps = {
id: "5d552d0058f193f2795fc814",
isFollowed: "active",
isStarred: "idle",
image: "./assets/images/avatar.png",
readingTime: 20,
name: "Walton Morton",
date: "Aug 9"
}
describe("Users Component", () => {
let wrapper;
beforeEach(() => (
wrapper = mount(<Users { ...initialProps }/>);
)}
it("updates the input's 'checked' property based upon a 'isStarred' prop", () => (
expect(wrapper.find("input").props().checked).toBeFalsy();
wrapper.setProps({ isStarred: "active" });
expect(wrapper.find("input").props().checked).toBeTruthy();
});
...etc
});
On a side note, you can use object destructuring and the spread syntax to vastly simply your React component code.
Getting this error
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {}
However, i think i shouldn't be getting this error because im using jest.fn. So im mocking the function.
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
// add the name of the prop, which in this case ites called onItemAdded prop,
// then use jest.fn()
const wrapper = shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
// console.log('props',wrapper.find('button').props());
wrapper.find('button').simulate('click');
expect(wrapper).toHaveBeenCalled(); // error happens when this executes
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js (using the component in this file)
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
I'm not 100% sure, but I believe you should do something like this:
describe('should simulate button click', () => {
it('should simulate button click', () => {
const mockedFunction = jest.fn();
const wrapper = shallow(<TodoAddItem onItemAdded={ mockedFunction } />);
wrapper.find('button').simulate('click');
expect(mockedFunction).toHaveBeenCalled();
});
});
You are testing if the onItemAdded function gets called when you click the <TodoAddItem /> component. So you have to mock it first using jest.fn and then check if the mocked function got called after you simulated the click.
For me works replacing the next one:
const setCategories = () => jest.fn();
With this one:
const setCategories = jest.fn();
I suppose that you should to set just jest.fn or jest.fn() in your code.
I am trying test a component in React w/ TypeScript using Jest and Enzyme.
My test is as follows:
import * as React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import { ButtonGroup } from '../../../src/components';
describe('.ButtonGroup', () => {
it('should render', () => {
const { wrapper } = setup({});
expect(wrapper.exists()).toBe(true);
});
it('should call the rightHandler handler on click', () => {
const onClickHandler = sinon.spy();
const wrapper = setup({ rightHandler: onClickHandler });
wrapper.find('a').simulate('click');
expect(onClickHandler).toHaveBeenCalled();
});
});
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return { wrapper };
};
const MOCK_LEFT = { type: 'reset', className: 'is-light', value: 'Reset' };
const MOCK_RIGHT = { type: 'button', className: 'is-primary', value: 'Search' };
However I am getting an error: TypeError: wrapper.find is not a function
The component I am testing looks like
import * as React from 'react';
const ButtonGroup = ({ leftBtn, rightBtn, leftHandler = null, rightHandler = null }) => (
<div className="field is-grouped is-grouped-right">
<p className="control">
<input type={leftBtn.type} className={'button ' + leftBtn.className} value={leftBtn.value} onClick={leftHandler} />
</p>
<p className="control">
<input type={rightBtn.type} className={'button ' + rightBtn.className} value={rightBtn.value} onClick={rightHandler} />
</p>
</div>
);
export default ButtonGroup;
I would like to essentially asset that on click, the action I expect is called.
Try
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return wrapper;
};
Doing return { wrapper } is the same as return { wrapper: wrapper } so it's not your DOM element anymore, but an object with a property wrapper containing your DOM element.
This is a small bin to illustrate the issue.