// JEST and Enzyme for React Native App
Also see part 2 here: Why Shallow Tests?
Well, hopefully you reading this are not given a short few days to write tests for something you never seen before. That was my experience, but it happened because I was trying to help my team hit the crazy deadline that was just over
Disclaimer
- Unless your APP / program is REALLY huge (like Facebook), manual testing may be more time-savvy than writing unit tests
- This article summarizes all my discoveries over 3 work days writing tests. Hopefully that gets your tests up faster
Benefits of testing
- Catch bugs you did not see before (eg. 2 exported functions with same name but from different files, and slightly different implementation).
- Understand code that you haven't seen before.
Table of Contents
- File Naming
- Test DOM props OR function returns?
- Mocking Functions
- Mocking Hooks
- async / awaits
- Debug failing tests
- Others
1. File Naming
For ease of reference, just add .test.
to your actual file(s) name. Mimicking the folder structure will help organize things too. In this case, we have a component ITerms
within the page IdemnityForSpaceTravel
.
2. Test DOM props OR function returns
This article will focus on mount() method from Enzyme. If you are just mocking function return values, don't waste time here! Jest documentation should be plenty to get started.
describe('@pages/IdemnityForSpaceTravel/ITerms', () => {
it('getIdemnityIterms() is called correctly', () => {
// const wrapper =
mount(
<Wrap>
<ITerms />
</Wrap>
);
expect(getIdemnityIterms).toBeCalled();
});
...
});
3. Mocking (nested) Functions
- mocking default export function
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
}));
- mocking named export function(s)
jest.mock('@pages/IdemnityForSpaceTravel/ITerms/config', () => ({
getIdemnityIterms: jest.fn(),
getUserType: jest.fn(),
});
- partial mocking named export function(s) Uses some actual functions from the file.
jest.mock('@pages/IdemnityForSpaceTravel/ITerms/config', () => ({
...jest.requireActual('@pages/IdemnityForSpaceTravel/ITerms/config'),
getIdemnityIterms: jest.fn(),
});
mocking a nested function (with Promise / without Promise)
This is for the case when you want to mock a function within the function on your page / component. Use the same methods as above, but find the right file to import from.mocking actions
Button presses:
wrapper.find(TouchableWithoutFeedback).prop('onPress')?.();
orwrapper.find(TouchableWithoutFeedback).simulate('click')
4. Mocking Hooks
Sometimes, Jest isnt that helpful in telling you why it fails...
- Custom Hooks
Let's say you had a custom hook useError.tsx
that has the structure of
export default () => {
...
return { ErrorComponent, getErrorComponent };
};
used in main file like const { ErrorComponent } = useError();
, do this:
jest.mock('@utils/index', () => {
return {
__esModule: true,
useError: () => ({ ErrorComponent: 'mocked_value' })
};
});
useState() or useEffect() hooks
In the case where you haveuseEffect(() => { ... }, [])
that runs on initial render, updating the state of the page / component, usewrapper.update()
to update the DOM props.redux useSelector() hooks
In this case, this is what you need in the.test.
file.
import * as redux from 'react-redux';
...
const mockUsername = 'zelenskyyy';
...
describe('@pages/IdemnityForSpaceTravel', () => {
beforeEach(() => {
const spy = jest.spyOn(redux, 'useSelector');
spy.mockReturnValue({
username: mockUsername
});
});
...
});
5. async / awaits
Flushing Promises are a good way to ensure the async calls are completed before you assert tests. In below example, we see how we mock test an async function getIdemnityIterms in (1) Promise Resolved, (2) Promise Rejected.
// definition for flushing promises
const flushPromises = () => new Promise(setImmediate)
// mock the function as a generic jest.fn
jest.mock('@pages/IdemnityForSpaceTravel/ITerms/config', () => {
return {
__esModule: true,
getIdemnityIterms: jest.fn(),
};
});
// tests
describe('@pages/IdemnityForSpaceTravel', () => {
it('promise works out', async () => {
// mock jest.fn implementation: Promise is resolved
(getIdemnityIterms as any).mockImplementation(() =>
Promise.resolve({ ITerms: 'this is my iterm' });
const wrapper = mount(
<Wrap>
<ITerms>
</Wrap>
);
await flushPromises();
wrapper.update();
expect(getIdemnityIterms).toBeCalled();
expect(wrapper.find(Text).prop('value')).toBe('this is my iterm');
// Finds the 'value' of Text component in ITerms
});
it('feeling betrayed', async () => {
// mock jest.fn implementation: Promise is rejected
(getIdemnityIterms as any).mockImplementation(() =>
Promise.reject());
const wrapper = mount(
<Wrap>
<ITerms>
</Wrap>
);
await flushPromises();
wrapper.update();
expect(getIdemnityIterms).toBeCalled();
expect(wrapper.find(Text).prop('value')).toBe('');
// Finds the 'value' of Text component in ITerms
});
...
});
6. Debug failing tests
1. Use Jest built-in Istanbul to see which lines need coverage.
Run Jest with --coverage
flag, open the coverage/Icov-report/index.html
in Finder / File Explorer and double-click it. This should give you all the files and how much coverage it has.
Open each file to see which lines are not covered.
Here, RED lines 44 and 45 means it is not covered.
2. Comment out everything
Comment out everything. Then uncomment each component in the render tree and run test. This helps you figure out where the test is failing. You can even do this at the prop level
3. Log the DOM Tree
console.log(wrapper.debug())
to see the whole render tree.
// Would you just look at the <View> ?
If you have a very deep-nested DOM tree like above, you can zoom into each part like
console.log(wrapper.find(Touchable).debug()) // find all Touchable component(s)
console.log(wrapper.find(Touchable).at(1).debug()) // find Touchable component at position 1 (0-indexed)
console.log(wrapper.find(Touchable).find(TextInput).debug()) // find the TextInput component inside the Touchable component
4. Take a break!
Sometimes you just forgot to save changes, or typo-ed somewhere.
7. Others
1. Jest configurations
Having looked at so much for tests in such short time, I just feel like
Go figure this yourself!
2. Running Individual tests
This is mainly to reduce time spent on debugging tests.
Option 1: Running test with flag
With this, you can get to run individual test files. For example, if you have a test file Spaghetti.test.tsx, run npm run test -- Spaghetti.test.tsx
Option 2: Adding a VSCode Plugin
Check this post on how to start it.
Pro: Quicker test debugging. Runs single tests instead of ALL existing tests.
Con: Initial setup time required.
3. Test Coverage
Run JEST --coverage
flag to see how much tests are covering. Things like if / else
branch, function call coverage.
4. React Testing Library
Some forums say it is better, maybe it is a viable alternative.
Top comments (0)