How do test async components with Jest? - reactjs

can anyone tell me how to wait in jest for a mocked promise to resolve when mounting a component that calls componendDidMount()?
class Something extends React.Component {
state = {
res: null,
};
componentDidMount() {
API.get().then(res => this.setState({ res }));
}
render() {
if (!!this.state.res) return
return <span>user: ${this.state.res.user}</span>;
}
}
the API.get() is mocked in my jest test
data = [
'user': 1,
'name': 'bob'
];
function mockPromiseResolution(response) {
return new Promise((resolve, reject) => {
process.nextTick(
resolve(response)
);
});
}
const API = {
get: () => mockPromiseResolution(data),
};
Then my testing file:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
expect(mountToJson(wrapper)).toMatchSnapshot();
// here is where I dont know how to wait to do the expect until the mock promise does its nextTick and resolves
});
});
The issue is that I the expect(mountToJson(wrapper)) is returning null because the mocked api call and lifecycle methods of <Something /> haven't gone through yet.

Jest has mocks to fake time travelling, to use it in your case, I guess you can change your code in the following style:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
jest.useFakeTimers(); // this statement makes sure you use fake timers
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
// skip forward to a certain time
jest.runTimersToTime(1);
expect(mountToJson(wrapper)).toMatchSnapshot();
});
});
Alternatively to jest.runTimersToTime() you could also use jest.runAllTimers()

As a workaround convert it from async to sync
jest.spyOn(Api, 'get').mockReturnValue({
then: fn => fn('hello');
});

Related

How to test a component that is conditionally rendered based on a hook value?

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();
})

How can I mock an imported React hook/module and test that it's being called properly on different test cases using Jest

I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});

Mock a custom service with jest and enzyme for a react component

I have a simple component that loads up some users from an api call, which I have abstracted in a service. Here is the Component:
export const Dashboard: FunctionComponent = () => {
const [users, setUsers] = useState<IUser[]>([]);
const [isLoading, setIsLoading] = useState(true);
const userService: UserService = UserService.get();
useEffect(() => {
userService.getUsers()
.then((data) => {
console.log(data)
setIsLoading(false)
setUsers(data)
})
.catch((error) => {
console.log("Could not load users: ", error);
setIsLoading(false)
setUsers([]);
});
}, []);
return (
isLoading
?
<div data-testid="loading">
<h4>Loading...</h4>
</div>
:
<div data-testid="users">
<UserList users={users}/>
</div>
);
}
export default Dashboard;
And my service looks like this:
export class UserService {
private static INSTANCE = new UserService();
private constructor() {}
public static get(): UserService {
return UserService.INSTANCE;
}
public async getUsers(): Promise<IUser[]> {
const response = await axios.get("api/users");
return response.data as IUsers[];
}
}
The reason I have extracted it in a .ts file is that I am planing to be reusing this service in another component and also add here other api calls.
So now I want to write a simple test for my Dashboard component, where I mock the UserService to return a promise and then test that my data-testid=users is rendered.
Here is my test:
configure({adapter: new Adapter()});
describe("User dashboard component", () => {
let userService: UserService;
const users = [
{
id: "0c8593e8-8fa6-4d40-b555-5ef812477c70",
name: "John",
age: 25
}
];
beforeAll(() => {
userService = UserService.get();
});
test("renders component", () => {
userService.getUsers = () => {
return Promise.resolve(users);
};
const dashboard = shallow(<Dashboard />);
expect(dashboard.find(<Dashboard />)).toBeTruthy();
expect(dashboard.find('[data-testid="users"]').length).toEqual(1);
expect(toJson(dashboard)).toMatchSnapshot();
});
test("loading", () => {
const dashboard = shallow(<Dashboard />);
expect(dashboard.find('[data-testid="loading"]').length).toEqual(1);
expect(toJson(dashboard)).toMatchSnapshot();
});
});
I don't want to mock the useState hook, but apparently my part where I resolve a Promise with users does nothing.
How do I achieve that? what is the best practice here? Thanks!
shallow doesn't support useEffect at this moment, mount should be used instead.
The component is rendered asynchronously and the test should be asynchronous too. Mocking methods by assignment is a bad practice because they cannot be restored, this results in test cross-contamination. A promise that makes it asynchronous should be exposed for chaining. In case of a spy it can be retrieved via Jest spy API.
It should be:
jest.spyOn(userService, 'getUsers').mockImplementation(() => Promise.resolve(users));
const dashboard = mount(<Dashboard />);
expect(userService.getUsers).toBeCalledTimes(1);
await act(async () => {
await userService.getUsers.mock.results[0].value;
});
...
Spies should be restored and cleared between tests in order for tests to not affect each other.

Jest mock a fetch function from a connected component

I have some code which works. However for my test I would like to mock the fetch that is done in the component.
The test
I am trying the following:
import ConnectedComponent from './Component';
import { render } from '#testing-library/react';
import user from '../__models__/user'; // arbitrary file for the response
// create a mock response
const mockSuccessResponse = user;
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
// Trying mock the refetch from http
jest.mock('./http', () => {
return {
refetch: () => ({
settingsFetch: () => mockFetchPromise,
})
}
});
it('renders', async () => {
const { getByText } = render(Component);
const title = await getByText('My title');
expect(title).toBeInTheDocument();
});
Error message
With this I receive the following error:
● Test suite failed to run
TypeError: (0 , _http.refetch)(...) is not a function
The Application code
This code is working fine in my application. To give you an example:
./http.js
import { connect } from 'react-refetch';
export async function fetchWithToken(urlOrRequest, options = {}) {
// some stuff
return response;
}
export const refetch = connect.defaults({
fetch: fetchWithToken,
});
./Component.jsx
import { refetch } from './http';
const Component = ({ settingsFetch }) => <AnotherComponent settingsFetch={settingsFetch} />);
const ConnectedComponent = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'http://some-url/api/v1/foo'
}
})
)(Component)
export default ConnectedComponent;
How can I mock this function to return a mocked Promise as the response?
Update: It's getting close by doing the following:
jest.mock('../helpers/http', () => ({
refetch: () => jest.fn(
(ReactComponent) => (ReactComponent),
),
}));
Now the error reads:
Warning: Failed prop type: The prop `settingsFetch` is marked as required in `ConnectedComponent`, but its value is `undefined`.
Which means I will probably have to provide the mocked responses for the fetches in there somewhere.
Jest itself is in charge of the modules. So in the following example you will see that the module coming from '../http' can be mocked.
You can then overwrite the props of that module by first adding the default props, and after that overwrite the ones you need with your own.
jest.mock('../http', () => {
return {
refetch: function(hocConf) {
return function(component) {
component.defaultProps = {
...component.defaultProps,
settingsFetch: {},
// remember to add a Promise instead of an empty object here
};
return component;
};
},
};
});

jest enzyme testing a component with async fetch to api

I'm using react, jest and enzyme with immutable. I'm trying to mount a component that fetching data from the API, and I'm having a little difficulties.
// FooListContainer.jsx
export default class FooListContainer extends Component {
constructor(props) {
super(props);
this.state = {
foos: List()
}
}
componetWillMount() {
manager.bringFooList()
.then(lst => this.setState({ foos: fromJS(lst) }))
.done();
}
render() {
return <FooList foos={this.state.foos} />
}
}
This is the ui component all it does is receive list and map them
// FooList.jsx
export default class FooList extends Component {
render() {
return (
<div>
{this.props.foos.map(item => <div>{item}</div>)}
</div>
);
}
}
Now I would like to test the data received from the fetch in FooListContainter is passed correctly to FooList.
// FooListContainer.test.jsx
describe('rendering', () => {
it('Should passed the data from the fetch to the FooList', () => {
const response = ['1', '2', '3'];
manager.bringFooList = jest.fn(() => {
return new Promise(resolve => {
return resolve(response);
});
})
const wrapper = mount(<FooListContainer />);
const fooList = wrapper.find(FooList);
expect(fooList.props().foos.size).toBe(3);
});
});
But the test fails because it expects the length to be 3 and it actual length is 0 from some reason.
I think that it has something to do with the fact that the fetch inside the container is async - so the test is not 'waiting' to the response and render is happening before the state change for the first time and the FooList receive an empty list.
I have tried to receive in the 'it' function an async callback as an argument and call it after the mount, like this:
// FooListContainer.test.jsx
describe('rendering', () => {
it('Should passed the data from the fetch to the FooList', (done) => {
const response = ['1', '2', '3'];
manager.bringFooList = jest.fn(() => {
return new Promise(resolve => {
return resolve(response);
});
})
const wrapper = mount(<FooListContainer />);
done();
const fooList = wrapper.find(FooList);
expect(fooList.props().foos.size).toBe(3);
});
});
But the above example did not work.
I would really appreciate every help you could give me.

Resources