I have built an application in react native, and am now going through the process of updating some old test suites. The test running an error is a component test using react test renderer to simulate the functionality of the screen.
Error Summary: One of the expect statements is throwing an error saying
Cannot read property 'call' of undefined
When the value exists and I can print out the property call like so
console.log(store.dispatch.mock.calls[6][0]);
and it gives me the expected value.
CODE:
//Imports
jest.spyOn(Date, "now").mockImplementation(() => 1479427200000);
const mockStore = configureStore([]);
describe("block1", () => {
it("test1", async done => {
try {
let component;
let store = mockStore(stores.SummaryR1);
store.dispatch = await jest.fn();
const mockDate = new Date(1466424490000);
const spy = jest.spyOn(global, "Date").mockImplementation(() => mockDate);
Date.now = jest.fn(() => 1466424490000);
await act(async () => {
component = await renderer.create(
<PaperProvider>
<Provider store={store}>
<Receive_Signature />
</Provider>
</PaperProvider>
);
});
const expected = await component.toJSON();
expect(expected).toMatchSnapshot();
await act(async () => {
//action
});
await act(async () => {
//action
});
await act(async () => {
//action
});
await act(async () => {
//action
});
await act(async () => {
//action
});
expect(store.dispatch).toHaveBeenCalledTimes(8);
expect(store.dispatch).toHaveBeenNthCalledWith(1, {results1});
expect(store.dispatch).toHaveBeenNthCalledWith(2, {results2});
expect(store.dispatch).toHaveBeenNthCalledWith(3, {results3});
expect(store.dispatch).toHaveBeenNthCalledWith(4, {results4});
expect(store.dispatch).toHaveBeenNthCalledWith(5, {results5});
expect(store.dispatch).toHaveBeenNthCalledWith(6, {results6});
expect(store.dispatch).toHaveBeenNthCalledWith(7, {results7} );
expect(store.dispatch).toHaveBeenNthCalledWith(8, {results8});
expect(navigateToScreen).toHaveBeenCalledTimes(1);
expect(navigateToScreen.mock.calls[0][0]).toEqual("Processor_Dashboard");
done();
} catch (error) {
done.fail(error);
}
}, 15000);
The error is forming on testing results7 on the 7th call.
Firstly I know there are 8 calls because of
expect(store.dispatch).toHaveBeenCalledTimes(8);
I can then also print out results7, and see that I have the correct data for results7. But when I run it in the jest expect statement I get the error:
Cannot read property 'call' of undefined
I have no idea why there is this error since all the other expects run, and if I comment out just the one statement the rest of the suite runs fine as well. For some reason it is only erroring out on the one expect statement.
Obviously the data has been removed, but does not affect how it would run.
Related
I am trying to write a test case. but I am trying to mock stripe npm but not getting success.
Write two basic tests and execute them you can see the error in the console
App.js file
useEffect(() => {
getStripeToken();
initializeStripe();
}, []);
const initializeStripe = () => {
initStripe({
publishableKey:'pk_te9ms7qP6N70007AHphRr',
merchantIdentifier: 'merchant.identifier',
urlScheme: 'your-url-scheme'
});
};
App.test.tsx
test('Stripe Payment render correctly', () => {
render(<StripePayment />);
});
test('Stripe Payment snapshot capture correctly', () => {
const componentTree = render(<StripePayment />).toJSON();
expect(componentTree).toMatchSnapshot();
});
jest.setup.js
import mock from '#stripe/stripe-react-native/jest/mock.js';
jest.mock('#stripe/stripe-react-native', () => mock);
Received following error
I am using jest and #testing-library/react-hooks to test hooks implemented with react-query in my React Native code.
The tests work ok, but at the end, I am getting:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
Here is my simplified code:
import { renderHook } from '#testing-library/react-hooks'
import React from 'react'
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const useSomething = () => {
return useQuery('myquery', () => 'OK')
}
beforeAll((done) => {
done()
})
afterAll((done) => {
done()
})
// test cases
describe('Testing something', () => {
it('should do something', async () => {
const queryClient = new QueryClient()
const wrapper = ({ children }: { children: React.ReactFragment }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
const { result, waitFor } = renderHook(() => useSomething(), { wrapper })
await waitFor(() => {
return result.current.isSuccess
})
expect(result.current.data).toBe('OK')
})
})
I tried using cleanup, done, unmount, etc. before each/all with no results. If I remove useQuery from useSomething, the problem disappears.
Any idea how to fix it?
This issue has been reported in the past here:
https://github.com/tannerlinsley/react-query/issues/1847
The issue is caused by the react-query garbage collection timer running, which defaults to 5 minutes. Solutions are, as described in the issue:
clearing the queryCache after each test:
afterEach(() => { queryClient.clear() });
setting cacheTime to 0 for your test, e.g. with: queryClient.setDefaultOptions({ queries: { cacheTime: 0 } })
using jest.useFakeTimers()
You could try defining a function like:
export function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
Then on your test before the expect:
await flushPromises();
More info here
When kept in the component body, the following code works fine. Inside useEffect, it checks the asyncstorage and dispatches an action (the function is longer but other checks/dispatches in the function are of the same kind - check asyncstorage and if value exists, dispatch an action)
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
I'm trying to move it to a custom hook but am having problems. The custom hook is:
const useGetUserSettings = () => {
const dispatch = useDispatch();
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
};
export default useGetUserSettings;
Then in the component where I want to call the above, I do:
import useGetUserSettings from './hooks/useGetUserSettings';
...
const getUserSettings = useGetUserSettings();
...
useEffect(() => {
getUserSettings();
}, [getUserSettings])
It returns an error:
getUserSettings is not a function. (In 'getUserSettings()', 'getUserSettings' is undefined
I've been reading rules of hooks and browsing examples on the internet but I can get it working. I've got ESlint set up so it'd show if there were an invalid path to the hook.
Try the following.
useEffect(() => {
if (!getUserSettings) return;
getUserSettings();
}, [getUserSettings]);
The hook doesn't return anything, so it's not surprising that the return value is undefined ;)
Below test is passing but I get the following warning twice and I don't know why. Could someone help me to figure it out?
console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
at printWarning (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:120:30)
at error (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:92:5)
at ../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14953:13
at tryCallOne (../../node_modules/react-native/node_modules/promise/lib/core.js:37:12)
at ../../node_modules/react-native/node_modules/promise/lib/core.js:123:15
at flush (../../node_modules/asap/raw.js:50:29)
import { fireEvent } from '#testing-library/react-native'
import { renderScreen } from 'test/render'
describe('screens/home', () => {
it('should render and redirect to the EventScreen', async () => {
const {
getByA11yLabel,
findByA11yLabel,
findAllByA11yLabel,
toJSON
} = renderScreen('Main')
expect(toJSON()).toMatchSnapshot('Default render')
const title = 'New event'
const titleInput = getByA11yLabel('event.title')
// Change title - sync fn
fireEvent.changeText(titleInput, title)
// Create button should be visible
const createButton = await findByA11yLabel('event.create')
expect(titleInput.props.value).toBe(title)
expect(createButton).toBeTruthy()
expect(toJSON()).toMatchSnapshot('Change title')
// Create event - async fn
fireEvent.press(createButton)
// The app should be redirected to the EventScreen
const titleInputs = await findAllByA11yLabel('event.title')
const upsertButton = await findByA11yLabel('event.upsert')
expect(toJSON()).toMatchSnapshot('Create event')
expect(titleInputs).toHaveLength(2)
expect(titleInputs[0].props.value).toBe('') // #MainScreen
expect(titleInputs[1].props.value).toBe(title) // #EventScreen
expect(upsertButton).toBeTruthy()
})
})
As far as I know, there is no need to wrap fireEvent with an act- link
findBy* also are automatically wrapped with act - link
Related issue in GitHub is still open
Dependencies:
react: 16.13.1
expo: 39.0.4
jest: 26.6.3
ts-jest: 26.4.4
jest-expo: 39.0.0
#testing-library/jest-native: 3.4.3
#testing-library/react: 11.2.2
#testing-library/react-native: 7.1.0
react-test-renderer: 16.13.1
typescript: 4.1.2
If you've exhausted all other debugging efforts and are pretty sure your code is written correctly, it may be related to react-native/jest-preset replacing global.Promise with a mock (see issue).
The solution to the problem, in this case, is to override/patch the jest preset to first save the original global Promise, apply the react-native/jest-preset and then restore the original Promise (overwriting the mocked version). This allowed me to use await in the tests that were unrelated to rendering without triggering the dreaded
console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
This snippet shows one way to perform this patch: https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938
I was facing the same problem. For my case I was using useEffect in my component. And while test it prompted me to wrap the rendering inside an act() call. Once I did that i.e. act(async () => ...) my initial problem was solved but I was getting the above mentioned error (Warning: You called act(async () => ...) without await.). I had to use await act(async () => ...) in my test to fix that. Though I am still not sure why it was required.
For reference I am adding a complete example component and corresponding test using await act(async () => ...);
LocationComponent.tsx
/** #jsx jsx */
import { jsx } from 'theme-ui';
import { FunctionComponent, useEffect, useState } from 'react';
type Coordinate = {
latitude: number;
longitude: number;
};
const LocationComponent: FunctionComponent<any> = () => {
const [coordinate, setCoordinate] = useState<Coordinate>();
const [sharedLocation, setSharedLocation] = useState<boolean>();
useEffect(() => {
let mounted = true;
if (!coordinate && navigator) {
navigator.geolocation.getCurrentPosition(function (position) {
setCoordinate({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
});
navigator.permissions
.query({ name: 'geolocation' })
.then(function (result) {
if (mounted) setSharedLocation(result.state === 'granted');
});
}
return () => (mounted = false);
});
return (
<>
<div>Location shared:{sharedLocation ? 'Yes' : 'No'}</div>
<div>Latitude:{coordinate?.latitude}</div>
<div>Longitude:{coordinate?.longitude}</div>
</>
);
};
export default LocationComponent;
LocationComponent.spec.tsx
import React from 'react';
import { render, waitFor } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import LocationComponent from '../../../../../src/components/scheduler/location/LocationComponent';
const TEST_COORDS = {
latitude: 41.8817089,
longitude: -87.643301,
};
global.navigator.permissions = {
query: jest
.fn()
.mockImplementationOnce(() => Promise.resolve({ state: 'granted' })),
};
global.navigator.geolocation = {
getCurrentPosition: jest.fn().mockImplementationOnce((success) =>
Promise.resolve(
success({
coords: TEST_COORDS,
})
)
),
};
describe("Location Component when location share is 'granted'", () => {
it('should display current location details', async () => {
await act(async () => {
const { getByText } = render(<LocationComponent />);
/*expect(
await waitFor(() => getByText('Location shared:Yes'))
).toBeInTheDocument();*/
expect(
await waitFor(() => getByText('Latitude:41.8817089'))
).toBeInTheDocument();
expect(
await waitFor(() => getByText('Longitude:-87.643301'))
).toBeInTheDocument();
});
});
});
I am trying to build an app in react native that is suppose to take take two inputs by a user and then make a query to an api and get information about the two inputs. I have been having trouble with redux and redux-thunk and specifically with async actions.
This is the code in my app that i am specifically having trouble with
export const fetchData = url => {
console.log("start Fetching");
return async dispatch => { // this is where the problem is
dispatch(fetchingRequest());
try {
const response = await fetch("https://randomuser.me/api/?results=10");
const json = await response.text();
if (response.ok) {
dispatch(fetchingSuccess(json));
console.log("JSON", json);
} else {
console.log("fetch did not resolve");
}
} catch (error) {
dispatch(fetchingFailure(error));
}
};
console.log("Fetched data");
};
Upon debugging the function, I have ended with finding that when the fetchData function is called the function will execute but the async dispatch that is being returned has undefined behavior.
The output in the debugger when the function is called should be
start Fetching
JSON file information/Error
but the output in the debugger is actually
start Fetching
This is the function in which fetchData is called in
_onPress = () => {
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
this is the mapDispatchToProps function that I have added. The problem is i do not know what to add inside the function.
const mapStatetoDispatch = (url, dispatch) => {
return {dispatch(fetchData(url))}; // do not know what to place in body of function
};
i have connected it in the component with
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
these are the action creators that I import, if needed
import {
fetchingSuccess,
fetchingRequest,
fetchingFailure,
fetchData
} from "../data/redux/actions/appActions.js";
Assuming you have added redux-thunk as a middleware, it looks like the errors are here:
_onPress = () => {
const { fetchData } = this.props;
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
and
const mapStatetoDispatch = dispatch => ({
fetchData: url => dispatch(fetchData(url)),
}};