Disable react-native-reanimated globally for tests - react-native

We'd like to disable react-native-reanimated animations for tests based on a context variable, especially when we do UI snapshot tests to avoid flakes. For example, if there's a loading spinner, we don't want variations of its spinning animation lifecycle to be diffed and considered a change in the UI.
We've tried to wrap useAnimatedStyle in our own hook to pass the updater as ()=>({}) if we're in a test env, but there are a variety of issues that we're calling a JS from the UI thread:
const useLibraryAnimatedStyle = <T extends AnimatedStyle>(
updater: () => T,
deps?: DependencyList | null,
) => {
const { areAnimationsEnabled } = useLibraryGlobals();
const enabled = useSharedValue(areAnimationsEnabled);
useEffect(() => {
if (enabled.value !== areAnimationsEnabled) {
enabled.value = areAnimationsEnabled;
}
}, [areAnimationsEnabled, enabled]);
return useAnimatedStyle(enabled.value ? updater : () => ({}), deps);
};
Error: Tried to synchronously call anonymous function from a different thread
Wrapping updater as a worklet didn't seem to work, because the useAnimatedStyle does that anyway according to the Reanimated documentation.

Turned out that I did have to add "worklet" to the updater i was passing in:
const animatedStyles = useLibraryAnimatedStyle(() => {
"worker";
return {
transform: [{ rotateZ: "100deg" }],
};
});
vs what you normally pass to useAnimatedStyle
const animatedStyles = useAnimatedStyle(() => ({
transform: [{ rotateZ: "100deg" }],
});

Related

React Native: 'Jest did not exit one second after the test run has completed' with #testing-library/react-hooks and react-query

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

Effector: ".use argument should be a function" on attach

effector throws error ".use argument should be a function" at getCurrent (node_modules/effector/effector/region.ts:27:5) on attach method while testing.
I'm trying integration testing with mocking of some initial state.
//babel config
module.exports = (api) => {
if (api.env('test')) {
config.plugins.push([
'module-resolver',
{
root: ['.'],
alias: {
effector-react: 'effector-react/scope',
},
},
]);
}
return config;
};
//store.ts
export const getUsersFx = attach({
effect: getUsersOriginalFx,
source: [$clientId],
mapParams: (_, [clientId]) => ({
clientId
}),
});
//component.tsx
import { render } from '#testing-library/react';
import { Provider } from 'effector-react/scope'
describe('TestableComponent', () => {
test('should increment counter after click', () => {
const scope = fork()
const rendered = render(
<Provider value={scope}>
<TestableComponent />
</Provider>
);
})
})
I think the reason is that Effector mocks Effect and under the hood uses .use for effect callback mock. But then it change it to null or undefined and Effector throws error that fn in .use(fn) must be function
Effector mocks getUsersFx => getUsersFx.use(mockFn <- it's undefined i think).
Effector version: 22
Looks like the reason is multiple passes of effector/babel-plugin which you using for tests - error with attach is similiar:
https://github.com/effector/effector/issues/601
Multiple passes of babel-plugin will be supported in the next minor release of effector - for now you can check your babel configuration for duplicated usage of effector/babel-plugin

Mock Linking.openURL in React Native it's never been called

I´m writing some tests for my app and I´m trying to mock Linking module. I'm using jest.
The Linking.canOpenURL mock it's working fine (toHaveBeenCalled is returning true), but openURL mock is never called.
function mockSuccessLinking() {
const canOpenURL = jest
.spyOn(Linking, 'canOpenURL')
.mockImplementation(() => Promise.resolve(true));
const openURL = jest
.spyOn(Linking, 'openURL')
.mockImplementation(() => Promise.resolve(true));
return { canOpenURL, openURL };
}
The problem is that openURL is not been called.
Here is the test:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
expect(openURL).toHaveBeenCalled();
});
And this the hook under test:
export function useApplyToJob(url) {
const [error, setError] = useState(null);
const apply = () => {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
setError(`Don't know how to open ${url}`);
}
});
};
return [apply, { error }];
}
Given canOpenURL returns a promise, you'll need to wait for the async to occur before testing if openURL has been called. react-hooks-testing-library ships a few async utils to help with this.
Generally it's preferred to use waitForNextUpdate or waitForValueToChange as they are a bit more descriptive of what the test is waiting for, but your hook is not updating any state in the successful case, so you will need to use the more general waitFor utility instead:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});
As a side note, destructuring result.current to access apply is not recommended. It may work now, but it does not take much refactoring before the apply you're calling is using stale values from a previous render.
Similarly, I'd recommend wrapping the apply() call in act, even though it does not update any state right now. It just makes refactoring easier in the future as well as keeping your tests more consistent when you're testing the error case (which will need an act call).
import { renderHook, act } from '#testing-library/react-hooks';
// ...
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
// Act
act(() => {
result.current[0]();
});
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});

React Native useEffect() re-renders too much

i have a useEffect function where a redux action is called and data is written to prop. My Problem is that useEffect loop many times and flooded the server with requests.
const { loescherData, navigation } = props;
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
setData(props.loescherData);
}
});
}, [loescherData]);
if i leave it blank the rendering is finished before receiving data and the content would not updated.
is there another way to work with this function?
loescherData won't be available right after calling your redux-action fetchLoescherDetails ... and changing component by setData will cause an infinite rendering cause your current useEffect has a dependency on loescherData
So I'd suggest you exec your redux-action onComponentDidMount by passing an empty-deps [] to your effect ... and then consume the output of you action in a different effect
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
// setData(props.loescherData);
}
});
}, []);
useEffect(() => {
if (loescherData) {
// do some with loescherData like setState
}
}, [loescherData]);

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.