Using querySelectorAll instead of querySelector for testing with toBeInTheDocument - reactjs

Is it possible to use querySelectorAll with jest react tests rather than individually selecting each component with querySelector and checking that they're in the document with toBeInTheDocument?
For example, testing a component like this:
const SomeComponent = () => (
<>
<p id='one'>one</p>
<p id='two'>two</p>
<p id='three'>three</p>
</>
)
Would otherwise require writing a test like this:
import React from 'react';
import {render} from '#testing-library/react';
import '#testing-library/jest-dom';
describe('Some Component', () => {
it('Should render all', () => {
const {container} = render(<SomeComponent/>);
const one = container.querySelector('#one');
const two = container.querySelector('#two');
const three = container.querySelector('#three');
expect(one).toBeInTheDocument();
expect(two).toBeInTheDocument();
expect(three).toBeInTheDocument();
});
});
I have a list of numerous elements which is starting to get quite lengthy.

Check the number of the p element and use for...loop to check each one of them is in the document or not. So you don't need to assert them one by one.
import React from 'react';
import { render } from '#testing-library/react';
import '#testing-library/jest-dom';
import { SomeComponent } from '.';
describe('Some Component', () => {
it('Should render all', () => {
const { container } = render(<SomeComponent />);
const matches = container.querySelectorAll('p');
expect(matches).toHaveLength(3);
matches.forEach((m) => {
expect(m).toBeInTheDocument();
});
});
});

Related

Testing with jest on component with custom hook

i'm trying to write test code for my component that uses a custom hook to seperate logic from view
The problem is that i cannot for whatever reason seem to actually mock this custom hook in a test.
Following is a code example of what im trying to do:
// click-a-button.tsx
import {useClickAButton} from "./hooks/index";
export const ClickAButton = () => {
const { handleClick, total } = useClickAButton();
return <button onClick={handleClick}>{total}</button>;
}
// hooks/use-click-a-button.tsx
import React, {useCallback, useState} from 'react';
export const useClickAButton = () => {
const [total, setTotal] = useState<number>(0);
const handleClick = useCallback(() => {
setTotal(total => total + 1);
}, []);
return {
handleClick,
total,
};
}
// click-a-button.test.tsx
import * as React from 'react';
import {act} from "react-dom/test-utils";
import {render} from "#testing-library/react";
import {useClickAButton} from './hooks/index'
import {ClickAButton} from "./index";
const hooks = { useClickAButton }
test('it runs with a mocked customHook',() => {
const STATE_SPY = jest.spyOn(hooks, 'useClickAButton');
const CLICK_HANDLER = jest.fn();
STATE_SPY.mockReturnValue({
handleClick: CLICK_HANDLER,
total: 5,
});
const component = render(<ClickAButton />);
expect(component.container).toHaveTextContent('5');
act(() => {
component.container.click();
});
expect(CLICK_HANDLER).toHaveBeenCalled();
})
When running the test, neither of the expects is fulfilled.
Context gets to be 0 instead of the mocked 5 and the CLICK_HANDLER is never called.
All in all it seems that the jest.spyon has no effect.
Please help
it seems i found the answer myself.
// right after imports in test file
jest.mock('./hooks')
is all that it took!

React testing library using beforeAll to render cannot find item on 2nd test

The first tests passes.
The 2nd one I get error:
import React from 'react';
import { render, fireEvent, waitFor, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import '#testing-library/jest-dom/extend-expect';
import WrapProvider from '__tests__/__utils__/WrapProvider';
import PageMovies from 'src/pages/movies/index';
import { dataTestid as dataTestIdMoviesCount } from 'src/components/Specific/Movies/MoviesCount';
import { dataTestid as dataTestidDropdown } from 'src/components/Specific/Movies/SearchMovies/DropdownMovieGenres';
describe('<PageMovies>', () => {
beforeAll(async () => {
render(
<WrapProvider>
<PageMovies />
</WrapProvider>
);
await waitFor(() => screen.getByTestId(dataTestIdMoviesCount)); // success
});
describe('onload', () => {
it('Should have search results', () => {
expect(screen.getByTestId(dataTestIdMoviesCount)).toHaveTextContent(/ [1-9]/); //success
});
});
describe('dropdown and search', () => {
it('Should have dropdown', () => {
const $dropdown = screen.getByTestId(dataTestidDropdown); // <- error - not found.
// userEvent.selectOptions(screen.getByTestId(dataTestidDropdown), '28');
// expect((screen.getByLabelText('Action') as HTMLOptionElement).selected).toBeTruthy();
});
});
});
error: TestingLibraryElementError: Unable to find an element by: [data-testid="movies-dropdown"]
Yet this element does exist on the page onload
<div name="genre" style="width:400px" data-testid="movies-dropdown" role="combobox" aria-expanded="false" class="ui compact fluid multiple search selection dropdown">
Am I using screen.getByTestId correctly?
It seems there are 3 ways to use this.
Which is the right way?
import { getByTestId } from '#testing-library/react';
const { getByTestId } = render(<MyComponent/>);
import { screen } from '#testing-library/react'; screen.getByTestId()
I think this is an answer you need:
https://github.com/testing-library/react-testing-library/issues/541#issuecomment-562601514
According to this comment all you need to do is changing import:
#testing-library/react to #testing-library/react/pure to skip a cleanup after each test.
Perhaps dropdown is not appearing on initial load, you could findByTestId (which is a promise which searches for 1000ms)
Ideally you want to use the returned function from render(). There are a few exceptions such as when a React.portal is used.
In fact, you want to refrain from using data-testid, it's best to search through what a user can access, such as accessibility roles/name and text.

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

How do you use Enzyme to check for changes to a React Hooks component after onClick?

I am trying to write a simple integration test in my 100% React Hooks (React v16.12) project with Enzyme (v3.10), Jest (v24.0) and TypeScript where if I click a button component in my App container, another component displaying a counter will go up by one. The current value of the counter is stored in the state of the App container (see snippets below).
Basically, I mount the App component to render its children, then try to simulate a click on the button with Enzyme and check the props of the counter display component to see if its value has gone up. But nothing happens. Not only does the onClick handler not get called but I don't seem to be able to retrieve the value prop I pass to the PaperResource component. So basically I can't test the counter display changes when I click on the button in my Enzyme integration test! The test asserts that the value prop goes from 0 to 1, but this assertion fails without an error per seenter code here. Is this because Enzyme support for Hooks is still not there yet or am I doing something daft here? When I run the app on my browser, everything works as expected.
Here's my integration test
import React from 'react';
import App from './App';
import { mount, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import MakePaperButton from './components/MakePaperButton';
import PaperResource from './components/PaperResource';
describe('App', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
act(() => {
wrapper = mount(<App />);
});
});
describe('when make paper button is clicked', () => {
beforeEach(() => {
act(() => {
wrapper.find('.make-paper__button').simulate('click');
});
});
it('should increase paper resource', () => {
expect(wrapper.find('.resources__paper').prop('value')).toEqual(1);
});
});
});
And here is my React code
import React, { useState } from 'react';
import './App.scss';
import MakePaperButton from './components/MakePaperButton';
import PaperResource from './components/PaperResource';
const App: React.FC = () => {
const [ resources, setResources ] = useState({
paper: 0,
});
const handleMakePaperButtonClick = () => {
setResources({
...resources,
paper: resources.paper + 1,
});
};
return (
<div className="App">
<MakePaperButton onClick={handleMakePaperButtonClick} />
<div className="resources">
<PaperResource value={resources.paper} />
</div>
</div>
);
}
export default App;
My components are very simple
// PaperResource.tsx
import React from 'react';
export default (props: { value: number }) => (
<div className="resources__paper">
<span>Paper: {props.value}</span>
</div>
);
// MakePaperButton.tsx
import React from 'react';
export default (props: { onClick: () => void }) => (
<div className="make-paper__button">
<button onClick={props.onClick}>Make Paper</button>
</div>
);
The only solution I've found so far is wrapping the expect statement in a setTimeout().
it('should increase paper resource', () => {
setTimeout(() => {
expect(wrapper.find('.resources__paper').prop('value')).toEqual(1);
}, 0);
});

Props aren't passing inside component in test cases written with Jest and Enzyme

This is my test case
import React from 'react';
import { mount } from 'enzyme';
import CustomForm from '../index';
describe('Custom Form mount testing', () => {
let props;
let mountedCustomForm;
beforeEach(() => {
props = {
nav_module_id: 'null',
};
mountedCustomForm = undefined;
});
const customform = () => {
if (!mountedCustomForm) {
mountedCustomForm = mount(
<CustomForm {...props} />
);
}
return mountedCustomForm;
};
it('always renders a div', () => {
const divs = customform().find('div');
console.log(divs);
});
});
Whenever I run the test case using yarn test. It gives the following error TypeError: Cannot read property 'nav_module_id' of undefined.
I have already placed console at multiple places in order to see the value of props. It is getting set. But it couldn't just pass through the components and give the error mentioned above.
Any help would be appreciated been stuck for almost 2-3 days now.
You have to wrap the component that you want to test in beforeEach method such that it becomes available for all the 'it' blocks, and also you have to take the mocked props that you think you are getting into the original component.
import React from 'react'
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import {Map} from 'immutable'
import {ClusterToggle} from '../../../src/MapView/components/ClusterToggle'
describe('component tests for ClusterToggle', () => {
let dummydata
let wrapper
let props
beforeEach(() => {
dummydata = {
showClusters: true,
toggleClustering: () => {}
}
wrapper = shallow(<ClusterToggle {...dummydata} />)
props = wrapper.props()
})
describe(`ClusterToggle component tests`, () => {
it(`1. makes sure that component exists`, () => {
expect(wrapper).to.exist
})
it('2. makes sure that cluster toggle comp has input and label', () => {
expect(wrapper.find('input').length).to.eql(1)
expect(wrapper.find('label').length).to.eql(1)
})
it('3. simulating onChange for the input element', () => {
const spy = sinon.spy()
const hct = sinon.spy()
hct(wrapper.props(), 'toggleClustering')
spy(wrapper.instance(), 'handleClusterToggle')
wrapper.find('input').simulate('change')
expect(spy.calledOnce).to.eql(true)
expect(hct.calledOnce).to.eql(true)
})
})
})

Resources