React Testing Library Unit Test Case: Unable to find node on an unmounted component - reactjs

I'm having issue with React Unit test cases.
React: v18.2
Node v18.8
Created custom function to render component with ReactIntl. If we use custom component in same file in two different test cases, the second test is failing with below error.
Unable to find node on an unmounted component.
at findCurrentFiberUsingSlowPath (node_modules/react-dom/cjs/react-dom.development.js:4552:13)
at findCurrentHostFiber (node_modules/react-dom/cjs/react-dom.development.js:4703:23)
at findHostInstanceWithWarning (node_modules/react-dom/cjs/react-dom.development.js:28745:21)
at Object.findDOMNode (node_modules/react-dom/cjs/react-dom.development.js:29645:12)
at Transition.performEnter (node_modules/react-transition-group/cjs/Transition.js:280:71)
at node_modules/react-transition-group/cjs/Transition.js:259:27
If I run in different files or test case with setTimeout it is working as expected and there is no error. Please find the other configs below. It is failing even it is same test case.
setUpIntlConfig();
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
jest.clearAllMocks();
server.close();
cleanup();
});
Intl Config:
export const setUpIntlConfig = () => {
if (global.Intl) {
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
} else {
global.Intl = IntlPolyfill;
}
};
export const RenderWithReactIntl = (component: any) => {
return {
...render(
<IntlProvider locale="en" messages={en}>
{component}
</IntlProvider>
)
};
};
I'm using msw as mock server. Please guide us, if we are missing any configs.
Test cases:
test('fire get resource details with data', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});

Can you try these changes:
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitFor(() => expect(screen.getByText(/loading data.../i)).not.toBeInTheDocument());
const viewResource = screen.getAllByText(/view resource/i);
act(() => {
fireEvent.click(viewResource[0]);
});
await waitFor(() => expect(screen.getByText(/loading/i)).not.toBeInTheDocument());
expect(screen.getByText(/benedicte masson/i)).toBeVisible();
});
I've got into the habit of using act() when altering something that's visible on the screen. A good guide to here: https://testing-library.com/docs/guide-disappearance/
Using getBy* in the waitFor() blocks as above though, you may be better off specifically checking the text's non-existence.
Without seeing your code it's difficult to go any further. I always say keep tests short and simple, we're testing one thing. The more complex they get the more changes for unforeseen errors. It looks like you're rendering, awaiting a modal closure, then a click, then another modal closure, then more text on the screen. I'd split it into two or more tests.

Related

Testing InfiniteScroll component from react-infinite-scroller

I'm using react-infinite-scroller to make several calls to a paginated endpoint.
I haven't found any way to test the function that is triggered when a new request has to be done to the server.
I've taken a look at the github repository, but there is no information about how to trigger the function loadMore from a test.
The actual code looks like this:
<PaginatedList
data-testid="paginated-list-testid"
>
<InfiniteScroll
pageStart={0}
loadMore={this._nextPage}
hasMore={hasMore}
loader={<div key={0}>Loading...</div>}
useWindow={false}
>
{this._renderElements()}
</InfiniteScroll>
</PaginatedList>
The _nextPage function looks like follow:
async _nextPage() {
const { hasMore} = this.state;
const { externalCondition } = this.props;
if (hasMore) {
const response = externalCondition ? await this.repositoryA.next() : await this.repositoryB.next();
const nextElements = response.elements;
this.setState({
elements: nextElements,
loading: false,
hasMore: response.hasNext,
});
}
}
I wanna test that given an externalCondition the function this.repositoryA.next() is called. However, my test does not even reach this method.
This is my test:
test('Should\'ve called the repositoryA.next()', async () => {
const sampleUserId = 1;
const paginatedListTestId = "paginated-list-testid";
const renderComponent = render(
<PaginatedList
userId={sampleUserId}
/>,
);
const { getByTestId } = renderComponent;
fireEvent.scroll(getByTestId(paginatedListTestId), { target: { scrollY: 9000 } });
});
I know that my test does not have an assert yet, but I was debugging and I wasn't able to reach my function.
Neither scrolling to 9000 nor -9000.
Any help is welcome.
(The code has been a little bit changed due to privacy, so please, obviate any typo or mismatching variable names)

Test a component with useState and setTimeout

Code structure is as same as given below:
FunctionComponent.js
...
const [open, handler] = useState(false);
setTimeout(() => {handler(true);}, 2000);
...
return (
...
<div className={active ? 'open' : 'close'}>
)
comp.test.js
jest.useFakeTimers();
test('test case 1', () => {
expect(wrapper.find('open').length).toBe(0);
jest.advanceTimersByTime(2000);
expect(wrapper.find('open').length).toBe(1);
jest.useRealTimers();
});
The problem is that the expression written in bold in test is saying the length of open class is still 0, so actual and expected are not meeting.
You want to test the outcome of the hook and not the hook itself since that would be like testing React. You effectively want a test where you check for if the open class exists and then doesn't exist (or vice versa), which it looks like you're trying.
In short, to solve your issue you need to use ".open" when selecting the class. I would also suggest using the .exists() check on the class instead of ".length()" and then you can use ".toBeTruthy()" as well.
You could look into improve writing your tests in a Jest/Enzyme combined format as well:
import { shallow } from 'enzyme';
import { FunctionComponent } from './FunctionComponent.jsx';
jest.useFakeTimers();
describe('<FunctionCompnent />', () => {
const mockProps = { prop1: mockProp1, prop2: mockProp2, funcProp3: jest.fn() };
const wrapper = shallow(<FunctionComponent {...mockProps} />);
afterEach(() => {
jest.advanceTimersByTime(2000);
});
afterAll(() => {
jest.useRealTimers();
});
it('should render as closed initially', () => {
expect(wrapper.find('.close').exists()).toBeTruthy();
// you could also add the check for falsy of open if you wanted
// expect(wrapper.find('.open').exists()).toBeFalsy();
});
it('should change to open after 2 seconds (or more)', () => {
expect(wrapper.find('.open').exists()).toBeTruthy();
// you could also add the check for falsy of close if you wanted
// expect(wrapper.find('.close').exists()).toBeFalsy();
});
});
EDIT: Sorry realised I wrote the test backwards after checking your code again, they should be fixed now.

Why is jest.isolateModules not working as expected?

I want to move away from using jest.resetModules since I want to reset two modules, but keep one through all the runs.
My original code to get things working as such was bad like this:
beforeEach(() => {
jest.resetModules();
require('hooks/useMyProvider');
const useMyProvider = require('#/');
api = useMyProvider(); // scoped above
})
Then in each test, I would do something like this:
// Mock hook since we can't spy on a function
jest.doMock('#/hooks/useActive', () => {
return () => {
return [true]
}
});
// re-require the component that requires useActive so it pulls latest mock
let MyComponent = require('#/components/MyComponent')
const { container } = render(<MyComponent />);
I tried refactoring to having useMyProvider defined once at top of file and removing jest.resetModules from the beforeEach;
Then I did this:
let MyComponent;
jest.isolateModules(() => {
jest.doMock('#/hooks/useActive', () => {
return () => {
return [true]
}
});
MyComponent = require('#/components/MyComponent')
});
const { container } = render(<MyComponent />);
When I did this in two tests, and swapped out true for false in the return, whichever test came first would stick. I expected it to change each time since I had isolated the modules.

How to test in Jest the MediaQuerList.addListener

I would like to test that my function has been triggered properly (with the right parameters) but I can't find a way to make it...
I have a custom addEventListener take the name of the media query, the media query itself and a dispatch function
// ... Inside my class
addEventListener(name, mediaQuery, dispatch) {
// Initialize the mediaQueryList and store it in our list
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addListener(
mediaQueryListEvent => this.onScreenChange(name, mediaQueryListEvent)
);
this.mediaQueries.set(name, {
mediaQueryList,
dispatch
});
// Then we look even for the first time on which breakPoint we are
this.searchAndDispatchBreakpoint(name, mediaQueryList);
}
Any idea how I can test that my onScreenChange has been properly called with the right arguments?
Ok so after hours and hours of search and tries, I figured out a way.
I succeed in mocking the window.matchMedia and the addListener inside
Once done, I just have to test the arguments sent to my method.
Here is my jest test
it('should test the media query addListener method', () => {
const bpManager = new BreakpointManager();
const mediaQuery = 'max-width: 1080px';
const name = 'name';
const mediaQueryListEvent = 'mediaQueryListEvent';
bpManager.onScreenChange = jest.fn();
window.matchMedia = jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: e => e(mediaQueryListEvent),
removeListener: jest.fn(),
}));
bpManager.addEventListener(name, mediaQuery, dispatch);
expect(window.matchMedia).toHaveBeenCalledWith(mediaQuery);
expect(bpManager.onScreenChange).toBeCalledWith(name, mediaQueryListEvent);
});
And my method addEventListener
addEventListener(name, mediaQuery, dispatch) {
// Initialize the mediaQueryList and store it in our list
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addListener(
mediaQueryListEvent => this.onScreenChange(name, mediaQueryListEvent)
);
this.mediaQueries.set(name, {
mediaQueryList,
dispatch
});
// Then we look even for the first time on which breakPoint we are
this.searchAndDispatchBreakpoint(name, mediaQueryList);
}

how to change jest mock function return value in each test?

I have a mock module like this in my component test file
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => true,
guidanceEnabled: () => true
}));
these functions will be called in render function of my component to hide and show some specific feature.
I want to take a snapshot on different combinations of the return value of those mock functions.
for suppose I have a test case like this
it('RowListItem should not render navigation and guidance options', () => {
const wrapper = shallow(
<RowListItem type="regularList" {...props} />
);
expect(enzymeToJson(wrapper)).toMatchSnapshot();
});
to run this test case I want to change the mock module functions return values to false like this dynamically
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => false,
guidanceEnabled: () => false
}));
because i am importing RowListItem component already once so my mock module wont re import again. so it wont change. how can i solve this ?
You can mock the module so it returns spies and import it into your test:
import {navigationEnabled, guidanceEnabled} from '../../../magic/index'
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
Then later on you can change the actual implementation using mockImplementation
navigationEnabled.mockImplementation(()=> true)
//or
navigationEnabled.mockReturnValueOnce(true);
and in the next test
navigationEnabled.mockImplementation(()=> false)
//or
navigationEnabled.mockReturnValueOnce(false);
what you want to do is
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
describe('test suite', () => {
it('every test', () => {
navigationEnabled.mockReturnValueOnce(value);
guidanceEnabled.mockReturnValueOnce(value);
});
});
you can look more about these functions here =>https://facebook.github.io/jest/docs/mock-functions.html#mock-return-values
I had a hard time getting the accepted answers to work - my equivalents of navigationEnabled and guidanceEnabled were undefined when I tried to call mockReturnValueOnce on them.
Here's what I had to do:
In ../../../magic/__mocks__/index.js:
export const navigationEnabled = jest.fn();
export const guidanceEnabled = jest.fn();
in my index.test.js file:
jest.mock('../../../magic/index');
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
import { functionThatReturnsValueOfNavigationEnabled } from 'moduleToTest';
it('is able to mock', () => {
navigationEnabled.mockReturnValueOnce(true);
guidanceEnabled.mockReturnValueOnce(true);
expect(functionThatReturnsValueOfNavigationEnabled()).toBe(true);
});

Resources