React Native Test : Nock and Testing Library - react-native

I am trying to do screen testing with react native while mocking the api responses with Nock. After a call to the api my component is supposed to display some text fields. It works on the app but I can't make the test working.
Here is the component code :
export const CreationPage = () => {
const [publicSuites, setPublicSuite] = useState<Suite[]>([])
useEffect(() => {
axios.get('http://localhost:8080/api/suites').then((response) => {
console.log('It worked !', response.data)
setPublicSuite(response.data)
}, (error) => {
console.log('Oups something went wrong :', error)
})
}, [])
return (
<View>
{publicSuites.map((suite) =>
<Card key={suite.title}>
<Text>{suite.title}</Text>
</Card>
)}
</View>
)
}
And here the testing code :
it("should display public & private (hidden but visible in DOM) elements at launch", async () => {
render(<CreationPage/>);
nock('http://localhost:8080')
.get('/api/suites')
.reply(200, [{title: "Public Suite Test"}])
// Tab Switch cannot be tested
await waitFor(() => {
expect(screen.getByText("Public Suite Test")).toBeTruthy()
})
});
The logs I get :
console.log
Oups something went wrong : AxiosError {
port: 8080,
address: '127.0.0.1',
syscall: 'connect',
code: 'ECONNREFUSED',
...
Error: Unable to find an element with text: Public Suite Test

Related

how to test component with setState hook inside async api call in useEffect(func, [])

I'm facing a problem unit-testing a component with react-native-testing-library.
I have a component like this:
// components/TestComponent.js
function TestComponent() {
const [data, setData] = useState();
useEffect(() => {
clientLibrary.getData()
.then((result) => { setData(result.data); } )
.catch((err) => { //handle error here } )
}, []);
render (
<ListComponent
testID={"comp"}
data={data})
renderItem={(item) => <ListItem testID={'item'} data={item} />}
/>
);
}
And I test it like this:
// components/TestComponent.test.js
it('should render 10 list item', async () => {
const data = new Array(10).fill({}).map((v, idx) => ({
id: `v_${idx}`,
}));
const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
return Promise.resolve(data);
});
const {queryByTestId, queryAllByTestId} = render(
<TestComponent />,
);
expect(await queryByTestId('comp')).toBeTruthy(); // this will pass
expect(await queryAllByTestId('item').length).toEqual(10); // this will fail with result: 0 expected: 10
}); // this failed
The test will fail/pass with
Attempted to log "Warning: An update to TestComponent inside a test was not wrapped in act(...). pointing to setData in useEffect.
I've tried wrapping the render with act(), the assertion with act(), not mocking the api call, wrapping the whole test in act(), but the error won't go away.
I have tried looking at testing-library docs/git/q&a for this case, scoured stackoverflow questions too, but I still can't make this test works.
Can anyone point me to the right direction to solve this?
A note: I'm not trying to test implementation detail. I just want to test that given a fetch result X, the component would render as expected, which is rendering 10 list item.
Your component is performing an asynchronous state update during mounting inside useEffect so the act of rendering has an asynchronous side effect that needs to be wrapped in an await act(async()) call. See the testing recipes documentation on data fetching.
You can try something like this in your test:
it('should render 10 list item', async () => {
// Get these from `screen` now instead of `render`
const { queryByTestId, queryAllByTestId } = screen
const data = new Array(10).fill({}).map((v, idx) => ({
id: `v_${idx}`,
}));
const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
return Promise.resolve(data);
});
await act(async () => {
render(
<TestComponent />
);
})
expect(await queryByTestId('comp')).toBeTruthy();
expect(await queryAllByTestId('item').length).toEqual(10);
});

Testing app containing AppLoading component

I am new to the world of unit testing and I have just began to write tests for my React Native (Expo) app. After doing research I have finally landed in using Jest and React Native Testing Library.
Consider the following that uses the AppLoading component.
const App: React.FC = () => {
const [resourcesHasLoaded, setResourcesHasLoaded] = useState<boolean>(false);
const cacheResources = useCallback(async (): Promise<any> => {
const images = [require('./assets/icon.png')];
const cacheImages = images.map((image) => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all([cacheImages]);
}, []);
if (resourcesHasLoaded) {
return <Text>Hello world</Text>;
}
return (
<AppLoading
startAsync={cacheResources}
onError={console.warn}
onFinish={() => setResourcesHasLoaded(true)}
/>
);
};
When running my test, that looks like this:
describe('App.tsx', () => {
it('should be able to render', async () => {
render(<App />);
});
});
I end up with the following error (although, test passes):
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
So, I wrapped my `render` in with `act` the following way:
act(() => {
render(<App />);
});
... which resulted in the same error.
If I however wrap the onFinish-callback in my component the following way the test passes without warnings.
onFinish={() => act(() => setResourcesHasLoaded(true))}
But do I really want to pollute my React Component with test-specific functions? I saw no example of this, so I can only assume that this is bad practice.
Any suggestions here?
Update
I got the suggestion to use waitFor after my render by #Estus Flask in my comments. That did the trick... the test now passes.
https://callstack.github.io/react-native-testing-library/docs/api/#waitfor
describe('App.tsx', () => {
it('should be able to render', async () => {
const { findByText } = render(<MyApp />);
await waitFor(() => findByText('Hello world'));
});
});

Cannot read property 'Direction' of undefined, tests only

I just added TouchableOpacity to a component and the app is working fine, but my tests, using react-native-testing-library, fail to run:
● Test suite failed to run
TypeError: Cannot read property 'Direction' of undefined
at Object.Direction (node_modules/react-native-gesture-handler/Directions.js:3:39)
at Object.<anonymous> (node_modules/react-native-gesture-handler/GestureHandler.js:2:1)
I just removed and re-added react-native-gesture-handler with yarn, and ran pod install. Again, the app is working, but the tests fail to run.
I actually get the same error when using <Text onPress={() => onOptionPress(opt)} /> rather than TouchableOpacity.
component:
const SelectOptions = ({ field, dismissOverlay, onOptionPress }) => {
return (
<Overlay
isVisible
overlayStyle={styles.overlay}
height={"auto"}
onBackdropPress={dismissOverlay}
>
<View>
{field.options.map((opt, i) => (
<TouchableOpacity
style={styles.option}
key={i}
onPress={() => onOptionPress(opt)}
>
<Text>{opt}</Text>
</TouchableOpacity>
))}
</View>
</Overlay>
);
};
test:
describe("CardFormView", () => {
let wrapper, birthdayField;
beforeEach(() => {
wrapper = render(
<React.Fragment>
<CardFormView form={form} />
</React.Fragment>
);
birthdayField = wrapper.getByText("Add a Birthday Gift Card");
});
const message1 =
"...";
const message2 =
"...";
it("shows the options for a birthday card when clicked", () => {
fireEvent.press(birthdayField);
expect(wrapper.getByText(message1)).toBeDefined();
});
it("sets an option when clicked", () => {
fireEvent.press(birthdayField);
const firstOption = wrapper.getByText(message1);
fireEvent.press(firstOption);
expect(wrapper.queryByText(message2)).toBeNull();
expect(wrapper.getByText(message1)).toBeDefined();
});
});
This is because you are not mocking the react-navigation-gesture-handler
To use mock of react-navigation-gesture-handler you should add jestSetup.js from node_modules in jest.config.json or jest.config.js
setupFiles: [
"./node_modules/react-native-gesture-handler/jestSetup.js"
]
I found a reference from the following link and It's working for me.
https://github.com/software-mansion/react-native-gesture-handler/issues/344#issuecomment-489547513
For me just adding the setupFiles didn't work. I added setupFiles and transformIgnorePatterns at "jest" in package.json
Here the code to make the gestureHandler work, but I tested it with AsyncStorage and the storage stopped work. If you aren't using AsyncStorage I presume this code will work very well!
"setupFiles": [
"./node_modules/react-native-gesture-handler/jestSetup.js"
],
"transformIgnorePatterns": [
"/node_modules/(?!native-base)/"
]
My reference:
https://github.com/software-mansion/react-native-gesture-handler/issues/344
Updating package.json and reinstalling npm package worked for me.
"jest": {
"preset": "react-native",
"transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|#?react-navigation)"],
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
This is happening because you have to mock the NativeModules module from react-native. It can happen with several modules but it was happening to me specifically with the ImagePicker, Linking and #react-navigation/native. This is what I did to mock the native modules.
/src/testSetup.ts
import {NativeModules} from 'react-native';
NativeModules.RNGestureHandlerModule= {
attachGestureHandler: jest.fn(),
createGestureHandler: jest.fn(),
dropGestureHandler: jest.fn(),
updateGestureHandler: jest.fn(),
State: {},
Directions: {},
},
NativeModules.ImagePickerManager = {
showImagePicker: jest.fn(),
}
NativeModules.Linking = {
canOpenUrl: jest.fn().mockResolvedValue(true),
openUrl: jest.fn().mockResolvedValue(true)
}
NativeModules.Platform = {
OS: 'iOS'
}
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
jest.mock('react-native/Libraries/Animated/src/animations/TimingAnimation');
const mockNavigation = () => {
const mockedNavigate = jest.fn();
const mockedAddListener = jest.fn();
jest.mock('#react-navigation/native', () => ({ // #ts-ignore
...(jest.requireActual('#react-navigation/native')),
useNavigation: () => ({
navigate: mockedNavigate,
addListener: mockedAddListener
})
}));
return {mockedNavigate, mockedAddListener}
}
in your tests
import { fireEvent, act, render } = '#testing-library/react-native'
const {mockedNavigate, mockedAddListener} = mockNavigation()
test('Should navigate', () => {
const { queryByText } = render(<Component />)
fireEvent.press(getByText('View Page Button'))
expect(mockedNavigate).toHaveBeenCalledWith('Your Page Name')
expect(mockedAddListener).toHaveBeenCalled()
})
In my case, I was using react-native-cli when encountered this problem. I removed it and installed #react-native-community/cli instead. It fixed everything!

Calling a lib function and another function on single onpress in react native

I am working on a react native app where i'm using react-native share. Here I am taking a screenshot of a component and I want to share the screenshot on any of the social platform. But I want to take the screenshot and share that on one button onpress which i'm not being able to do. Here's the code I have now:
import {RNViewShot,captureScreen} from "react-native-view-shot"
import Share, {ShareSheet} from 'react-native-share'
class First extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
uri:''
}
}
screenshot = () => {
captureScreen({
format: "jpg",
quality: 0.8
})
.then(
uri => this.setState({ uri: uri }),
error => console.error("Oops, snapshot failed", error)
);
}
onCancel() {
console.log("CANCEL")
this.setState({visible:false});
}
onOpen() {
console.log("OPEN")
this.setState({visible:true});
}
render() {
let shareOptions = {
title: "React Native",
message: "Hola shareOptions",
url: "uri",
subject: "Share Link" // for email
};
let shareImageBase64 = {
title: "React Native",
message: "This is from VIP live 4D",
url: this.state.uri,
subject: "Share Link" // for email
};
return (
<View>
<Button
onPress={() => Share.open(shareImageBase64) } >
<Text>Share </Text>
</Button>
</View>
);
}
}
export default withNavigation(First)
Is there any way to call the screenshot function on the button onpress with the lib function it already has?
Can you try this (modified from your code):
screenshot=()=> {
captureScreen({
format: "jpg",
quality: 0.8
}).then(uri => {
this.setState(()=>{
return { uri: uri };
), ()=>{
const shareImageBase64 = {
title : "React Native",
message: "This is from VIP live 4D",
url : this.state.uri,
subject: "Share Link" // for email
};
Share.open(shareImageBase64);
};
);
};
From what I can tell, the issue was that the Share.open was given the "this.state.uri" value before it was set. By taking the value from "this.state" in the setState callback, we can be sure that the value have been set at this point.

Invariant Violation: Unable to find node on an unmounted component. Apollo

I have some problems testing a Component inside a Create React App that returns a Query Component, I'm using jest and enzyme for testing. The error that I get is Invariant Violation: Unable to find node on an unmounted component.. Any ideas with what I'm doing wrong? What I'm trying to get is to test that the query component will return an array of components based on the data received from the server.
I tried using the methods posted in this medium article, but I can't get it to work at all.
// The component
export class MyWrapper extends React.Component {
render() {
return (
<List divided verticalAlign="middle" >
<Query query={query} >
{({ data, loading, error, refetch }) => {
if (loading) return <Loader />;
if (error) return <ErrorMessage />;
// set refetch as a class property
this.refetch = refetch;
return data.response
.map(el => (
<MyComponent
el={el}
/>
));
}}
</Query>
</List>
);
}
}
export default compose(
...//
)(MyWrapper);
// The test file
import React from "react";
import { MockedProvider } from "react-apollo/test-utils";
import query from "path/to/query";
import { MyWrapper } from "../MyWrapper";
import { props } from "./props";
const mocks = {
request: {
query,
},
result: {
data: {
response: [
// data
]
}
}
};
describe("<MyWrapper />", () => {
describe("rendering", () => {
it("renders <MyComponent />'s", async () => {
const wrapper = mount(
<MockedProvider mocks={mocks} removeTypename>
<MyWrapper {...props} />
</MockedProvider>
);
await new Promise(resolve => setTimeout(() => resolve(), 1000));
wrapper.update();
console.log(wrapper.debug());
});
});
});
This is the code snippet I tried to reproduce:
const wait = require('waait');
it('should render dog', async () => {
const dogMock = {
request: {
query: GET_DOG_QUERY,
variables: { name: 'Buck' },
},
result: {
data: { dog: { id: 1, name: 'Buck', breed: 'poodle' } },
},
};
const component = renderer.create(
<MockedProvider mocks={[dogMock]} addTypename={false}>
<Dog name="Buck" />
</MockedProvider>,
);
await wait(0); // wait for response
const p = component.root.findByType('p');
expect(p.children).toContain('Buck is a poodle');
});
After Googling to solve this for myself I found this.
According to this Git Issue the problem is in enzyme-adapter-react-16. EthanJStark said that updating to enzyme version 1.5.0 corrected it. I can confirm that the error stopped.
tldr;package.json – "enzyme-adapter-react-16": "^1.1",
+ "enzyme-adapter-react-16": "^1.5.0",
I was getting Invariant Violation: Unable to find node on an unmounted component too with TypeScript and Next.js in the mix.
After creating an isolated project which worked, I knew it had to be my codebase.
The stack trace seemed to stem at invariant (node_modules/react-dom/cjs/react-dom.development.js:55:15).
So in my case, upgrading from "react-dom": "16.5.2" to "react-dom": "16.7.0" fixed the issue for me, along with re-creating my yarn.lock file.