I am trying to mock the useColorScheme hook from react native so I can control what values it returns. My code is below:
const mockColorScheme = jest.fn();
jest.mock('react-native/Libraries/Utilities/useColorScheme', () => ({
useColorScheme: mockColorScheme,
}));
it('Renders correct theme when user selects light', () => {
const wrapper = ({children}: any) => (
<ThemeProvider userIsUsingDarkMode={false} userIsUsingSystemTheme={false}>
{children}
</ThemeProvider>
);
const {result} = renderHook(() => useTheme(), {wrapper});
expect(result.current.theme).toBeDefined();
expect(result.current.theme?.text).toStrictEqual('#333');
mockColorScheme.mockImplementationOnce(() => 'dark');
expect(result.current.theme).toBeDefined();
expect(result.current.theme?.text).toStrictEqual('#fbfbfb');
});
I would expect this to work, but I get the following error:
TypeError: (0 , _reactNative.useColorScheme) is not a function
This comes from my ThemeProvider component:
export const ThemeProvider: FunctionComponent<ThemeProviderProps> = ({
children,
userIsUsingDarkMode,
userIsUsingSystemTheme,
}) => {
const isDarkMode = useColorScheme() === 'dark';
...
export const useTheme = () => {
return useContext(ThemeContext);
};
If anyone has any ideas as to how to mock this I would really appreciate it. Thank you.
I was struggled for hours to solve the same problem, and I think I found a solution.
All you have to do is to mock actual module for the hook.
const mockedColorScheme = jest.fn()
jest.mock("react-native/Libraries/Utilities/useColorScheme", ()=> ({
...jest.requireActual("react-native/Libraries/Utilities/useColorScheme"),
useColorScheme: mockedColorScheme
}))
it("renders useColorScheme hook with return value of 'dark'", () => {
mockedColorScheme.mockImplementationOnce(() => "dark")
const { result } = renderHook(() => mockedColorScheme())
expect(result.current).toBeDefined()
expect(result.current).toEqual("dark")
})
We could simply mock the module default export.
const mockedUseColorScheme = jest.fn();
jest.mock('react-native/Libraries/Utilities/useColorScheme', () => {
return {
default: mockedUseColorScheme,
};
});
Related
ParentComponent.js
`
import React, {useEffect} from 'react'
import store from '../store_path'
import loadable from '#loadable/component'
import useTokenHook from 'hook_path'
const ChildComponent = loadable(() => import('../childComponent_path'))
export const ParentComponent = () => {
const token = useTokenHook()
const [data, setData] = useState({})
const [showChild, setShowChild] = useState(false)
const lang = store(state => state.lang)
const apiCall = store(state => state.apicall)
const myFn = async() => {
const res = await apiCall();
setData(res)
setShowChild(true)
}
useEffect(() => { myFn() }, [])
return(
<>
{showChild ? <ChildComponent data={data} /> : 'No data found'}
</>
)
}
`
I want to write JEST test cases for this component.
I am not able to mock store and values from store i.e. 'lang', 'apiCall'
I want to set some default value to 'lang' and i want to 'apiCall' to return specific value.
Also how can I set value 'setShowChild' to 'true' as initial value in testcase file
I tried few approaches to mock store like:
`
jest.mock('../store_path', () => ({
lang: 'en',
apiCall: jest.fn(() => {return someValue })
}))
`
Here I am getting error as:
TypeError: (0, _store.default) is not a function
I also tried
`
const appStore = jest.mock('../store_path', () => jest.fn())
appStore.mockImplementation(() => ({
lang: 'en',
apiCall: jest.fn(() => {return someValue })
}))
`
And Here I am getting error as:
appStore.mockImplementation is not a function
It looks like you are trying to mock default export in which case it can be mocked something like below
jest.mock('../store_path', () => ({
default: jest.fn()
// value for first store() call
.mockReturnValueOnce('en')
// value for second store() call since it's async function
.mockReturnValueOnce(() => new Promise((resolve) => { resolve('apiReturnVal')}))
}))
I am working on a React Native application and am very new to testing. I am trying to mock a hook that returns a true or false boolean based on the current user state. I need to mock the return value of the authState variable, and based on that, I should check if the component is rendered or not. But the jest mock is returning the same value only
useAuth.ts
export const useAuthState = () => {
const [authState, setAuthState] = useState<AuthState>();
useEffect(() => {
return authentication.subscribe(setAuthState);
}, []);
return authState;
};
MyComponent.tsx
export const MyComponent = () => {
const authState = useAuthState();
if (!authState) {
return null;
}
return <AnotherComponent />
}
MyComponent.test.tsx
import { MyComponent } from "./MyComponent"
jest.mock('../use-auth-state', () => {
return {
useAuthState: () => false,
};
});
const TestComponent = () => <MyComponent />
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
let testRenderer: ReactTestRenderer;
act(() => {
testRenderer = create(<TestComponent />);
});
const testInstance = testRenderer.getInstance();
expect(testInstance).toBeNull()
})
})
This is working fine. But, I am not able to mock useAuthState to be true as this false test case is failing. Am I doing it right? I feel like I am messing up something.
You want to change how useAuthState is mocked between tests, right? You can set your mock up as a spy instead and change the mock implementation between tests.
It's also a little more ergonomic to use the render method from react-testing-library. The easiest way would be to give your component a test ID and query for it. Something like the below
import { MyComponent } from "./MyComponent"
import * as useAuthState from '../use-auth-state';
const authStateSpy = jest.spyOn(useAuthState, 'default');
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
// you can use .mockImplementation at any time to change the mock behavior
authStateSpy.mockImplementation(() => false);
const { queryByTestId } = render(<MyComponent />;
expect(queryByTestId('testID')).toBeNull();
})
I've created a common component and exported it, i need to call that component in action based on the result from API. If the api success that alert message component will call with a message as "updated successfully". error then show with an error message.
calling service method in action. is there any way we can do like this? is it possible to call a component in action
You have many options.
1. Redux
If you are a fan of Redux, or your project already use Redux, you might want to do it like this.
First declare the slice, provider and hook
const CommonAlertSlice = createSlice({
name: 'CommonAlert',
initialState : {
error: undefined
},
reducers: {
setError(state, action: PayloadAction<string>) {
state.error = action.payload;
},
clearError(state) {
state.error = undefined;
},
}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const error = useSelector(state => state['CommonAlert'].error);
const dispatch = useDispatch();
return <>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() =>
dispatch(CommonAlertSlice.actions.clearError())} />
{children}
</>
}
export const useCommonAlert = () => {
const dispatch = useDispatch();
return {
setError: (error: string) => dispatch(CommonAlertSlice.actions.setError(error)),
}
}
And then use it like this.
const App: React.FC = () => {
return <CommonAlertProvider>
<YourComponent />
</CommonAlertProvider>
}
const YourComponent: React.FC = () => {
const { setError } = useCommonAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <> ... </>
}
2. React Context
If you like the built-in React Context, you can make it more simpler like this.
const CommonAlertContext = createContext({
setError: (error: string) => {}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const [error, setError] = useState<string>();
return <CommonAlertContext.Provider value={{
setError
}}>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
{children}
</CommonAlertContext.Provider>
}
export const useCommonAlert = () => useContext(CommonAlertContext);
And then use it the exact same way as in the Redux example.
3. A Hook Providing a Render Method
This option is the simplest.
export const useAlert = () => {
const [error, setError] = useState<string>();
return {
setError,
renderAlert: () => {
return <MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
}
}
}
Use it.
const YourComponent: React.FC = () => {
const { setError, renderAlert } = useAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <>
{renderAlert()}
...
</>
}
I saw the similar solution in Antd library, it was implemented like that
codesandbox link
App.js
import "./styles.css";
import alert from "./alert";
export default function App() {
const handleClick = () => {
alert();
};
return (
<div className="App">
<button onClick={handleClick}>Show alert</button>
</div>
);
}
alert function
import ReactDOM from "react-dom";
import { rootElement } from ".";
import Modal from "./Modal";
export default function alert() {
const modalEl = document.createElement("div");
rootElement.appendChild(modalEl);
function destroy() {
rootElement.removeChild(modalEl);
}
function render() {
ReactDOM.render(<Modal destroy={destroy} />, modalEl);
}
render();
}
Your modal component
import { useEffect } from "react";
export default function Modal({ destroy }) {
useEffect(() => {
return () => {
destroy();
};
}, [destroy]);
return (
<div>
Your alert <button onClick={destroy}>Close</button>
</div>
);
}
You can't call a Component in action, but you can use state for call a Component in render, using conditional rendering or state of Alert Component such as isShow.
What would be the correct way to test that a component has updated its parent context?
Say from the example below, after MsgSender has been clicked, how can I verify that MsgReader has been updated?
import React from 'react'
import { render, act, fireEvent } from '#testing-library/react'
const MsgReader = React.createContext()
const MsgWriter = React.createContext()
const MsgProvider = ({ init, children }) => {
const [state, setState] = React.useState(init)
return (
<MsgReader.Provider value={state}>
<MsgWriter.Provider value={setState}>{children}</MsgWriter.Provider>
</MsgReader.Provider>
)
}
const MsgSender = ({ value }) => {
const writer = React.useContext(MsgWriter)
return (
<button type="button" onClick={() => writer(value)}>
Increment
</button>
)
}
describe('Test <MsgSender> component', () => {
it('click updates context', async () => {
const { getByRole } = render(
<MsgProvider init={1}>
<MsgSender value={2} />
</MsgProvider>,
)
const button = getByRole('button')
await act(async () => fireEvent.click(button))
// -> expect(???).toBe(2)
})
})
The cleanest way I've managed to come up with is to manually set the *.Providers, but I'm wondering if this is perhaps the wrong way to go about it.
it('click updates context with overrides', async () => {
let state = 1
const setState = (value) => {
state = value
}
const { getByRole } = render(
<MsgReader.Provider value={state}>
<MsgWriter.Provider value={setState}>
<MsgSender value={2} />
</MsgWriter.Provider>
</MsgReader.Provider>,
)
const button = getByRole('button')
expect(state).toBe(1)
await act(async () => fireEvent.click(button))
expect(state).toBe(2)
})
You need to create a customRender which gives you the ability to assert the state like this:
function customRender(ui, { init, ...options }) {
const [state, setState] = React.useState(init);
function wrapper({ children }) {
return (
<MsgReader.Provider value={state}>
<MsgWriter.Provider value={setState}>{children}</MsgWriter.Provider>
</MsgReader.Provider>
);
}
return {
...render(ui, { wrapper, ...options }),
state,
};
}
describe("Test <MsgSender> component", () => {
it("click updates context", async () => {
const { getByRole, state } = customRender(<MsgSender value={2} />);
const button = getByRole("button");
await act(async () => fireEvent.click(button));
expect(state).toBe(2)
});
});
I have this stateless React component:
...
const Providers = ({ onSelectFeedProvider, ... }) => {
const handleSelectFeedProvider = value => e => {
e.preventDefault();
onSelectFeedProvider({ target: { value } });
};
return {
<Row onClick={handleSelectFeedProvider(1)}>
...
</Row>
}
}
And the test:
import Row from 'components/Common/Row';
import Providers from './index';
jest.mock('components/Common/Row', () => 'Row');
let onSelectFeedProviderSpy = jest.fn();
let onSelectProviderSpy = jest.fn();
const initialProps = {
feedProvider: 0,
onSelectFeedProvider: () => onSelectFeedProviderSpy(),
selectedProvider: undefined,
onSelectProvider: () => onSelectProviderSpy()
};
const mockComponent = props => {
const finalProps = { ...initialProps, ...props };
return <Providers {...finalProps} />;
};
it('should call correctly', () => {
const wrapper = shallow(mockComponent());
wrapper.find(Row).simulate('click', 'what do I have to do here');
expect(onSelect).toHaveBeenCalledTimes(1);
});
How can I do to call the method correctly and pass the coverage? I think have tried all the possibilities. Any thoughts?
You don't have many options in this, one approach is to have onSelect injectable
const Component = ({onSelect}) => {
const handleSelect = value => e => {
e.preventDefault()
onSelect && onSelect({ target: { value } })
}
return <Row onClick={handleSelect(1)} />
}
Test
it('should call correctly', () => {
const spy = jest.fn()
const wrapper = shallow(mockComponent({onSelectProvider: spy}));
wrapper.find(Row).simulate('click', 'what do I have to do here');
expect(spy).toHaveBeenCalledTimes(1);
});