How to test API calls in react using jest and enzyme? - reactjs

I have a React container in which I am making the API call and would like to be able to test this using jest and enzyme but unsure how to.
This is my code:
import React from "react";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
class SearchContainer extends React.Component {
state = {
articles: []
};
performSearch = event => {
fetch(
`http://content.guardianapis.com/search?q=${event}&api-key=${API_KEY}`
)
.then(response => response.json())
.then(data => this.setState({ articles: data.response.results }));
};
render() {
return (
<Search
performSearch={this.performSearch}
articles={this.state.articles}
/>
);
}
}
export default SearchContainer;

That's a great thing about unit testing, they force you to write better code. So to properly test this component, you should do the following:
Extract performSearch from The component into a separate file e.g. api.js
Mock performSearch in your api.js file (jest: mock a module)
Now you can test that the fetch function was called.
Note that with this code organization you could separately test your API calls and your SearchContainer without calling your API service.

I would approach this by extracting performSearch out into a module that wraps fetch. See this great article on testing things you don't own.
After that, you may not need SearchContainer any more if you store the articles state within the Search component. Since you're already using dependency injection with the performSearch property, you can pass in a mock object in place of it and use jest.fn() to ensure it is called.
For example:
const fakePerformSearch = jest.fn();
const component = Shallow(<Search performSearch={fakePerformSearch}/>);
expect(fakePerformSearch).toHaveBeenCalled();
And then test your new fetch wrapper as you would any JavaScript.

A lot of the other answers recommend using Jest's import mocker or a mock function, however, this tests implementation over behavior.
It's better to stub the environment instead of the tools. Let's write a test using an HTTP interceptor like nock. The beauty of this is you can migrate to different data fetching tools or make changes the fetch behavior and get feedback from your tests.
// src/SearchContainer/SearchContainer.test.js
import React from "react";
import nock from "nock";
import {mount} from "enzyme";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
describe('<SearchContainer />', async () => {
it('searches for articles', () => {
const scope = nock('http://content.guardianapis.com')
.get('/search')
.query({'api-keys': API_KEY, {q: 'some article'}})
.reply(200, {
results: [...]
})
const wrapper = mount(<SearchContainer />);
const searchInput = wrapper.find('[data-test-id="search-input"]');
await searchInput.simulate('change', { target: { value: 'some article' } });
const articles = wrapper.find('[data-test-id="articles"]');
expect(articles.length > 0).toBe(true);
expect(scope.isDone()).toBe(true);
});
});
For a deeper dive on testing API calls, I wrote a blog post Testing Components that make API calls.

Related

How do I mock Graphql queries and set the loader false to test the react component when loading = false with Jest & Enzyme?

I'm using codegen on frontend to generate types and using graphql queries to fetch data in this format -
const { data, loading, error } = useSampleQuery({
variables: {
a: 1,
b: 2
}
})
I'm using jest & enzyme for testing my react components.
Unfortunately you didn't give a full code example or enough information to give a detailed answer.
Basically you need to mock your Graphql API. If you are using Apollo this is quite easy. Apollo provides you with a MockedProvider, which acts like the normal provider, but lets you define test data.
import TestRenderer from 'react-test-renderer';
import { MockedProvider } from '#apollo/client/testing';
import { GET_DOG_QUERY, Dog } from './dog';
const mocks = []; // We'll fill this in next
it('renders without error', () => {
const component = TestRenderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<Dog name="Buck" />
</MockedProvider>,
);
const tree = component.toJSON();
expect(tree.children).toContain('Loading...');
});
You can find this and more examples here: https://www.apollographql.com/docs/react/development-testing/testing/
As you are also testing an API you kind of leave the area of unit testing. It is more an integration test. If you want to do unit testing you need to decouple the API requests from the component which displays your data.

Getting global config to a react hook

I have a React App, that talks to several REST APIs.
I have refactored my app from redux-thunks to use react-query for the business logic of calling the APIs.
Watching videos on react-query, it was advised to abstract this into a custom hook.
So, for example:
//
// useTodos.js
import { useQuery } from 'react-query';
import { TodoApi } from 'my-api-lib';
import config from '../config';
const todoApi = new TodoApi(config.TODO_API_BASE_URL);
const useTodos = (params) =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
I have another App where I could use these hooks to also talk to the REST APIs. So I'd like to move the hooks into a common library. But the config is provided by the client. How do I get the config (TODO_BASE_API_URI) or even the "todoApi" instance, to the custom hook from the client?
In Redux I essentially dependency-injected the TodoApi instance at startup with "thunk with extra argument"
Is there a "hooky" way to get the global config to my custom hook?
The library (I assume it's my-api-lib) should export a function that expects the url (or any other config), and returns the useTodoApi hook.
In your common library:
import { useQuery } from 'react-query';
import { TodoApi } from './TodoApi';
export const createUseTodoApi = url => {
const todoApi = new TodoApi(url);
return params =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
}
In your apps:
import { createTodoApi } from 'my-api-lib';
import config from '../config';
export const useTodoApi = createUseTodoApi(config.TODO_API_BASE_URL);

Getting props through HOC when mounting in Enzyme

I am trying to test some code that uses firebase. I am implementing the firebase-mock library. The problem I'am encountering now is that most all the components I should test get the firebase instance from a HOC (I have a class with the firebase methods I am using that is provided through the context API in the index.js and consumed via a withFirebase HOC, the wrapped component will have firebase in its props).
In this case the code I am trying to test is the following:
// mount.js
import React, { Component } from 'react';
import { withFirebase } from '../../components/Firebase';
class mount extends Component {
state = {
data: null,
};
ref = this.props.firebase.db.ref('/testing');
componentDidMount() {
// Fetch from testing ref
this.ref.on('value', snap => {
this.setState({ data: snap });
});
}
componentWillUnmount() {
this.ref.off('value');
}
render() {
return <div />;
}
}
export default withFirebase(mount);
In my test file I'm doing the following:
describe('Component mount.js', () => {
it.only('fetches', () => {
const wrapper = mount(<Mount />);
console.log(wrapper.prop());
console.log(wrapper.state().data);
});
});
This fails because this.props.firebase is null.
How could I solve this so that I can continue and finally mock firebase calls as i was intending.
I'm guessing that the problem is how to use the Context API in Enzyme, but I'm not sure.
The message you are getting is because enzyme.mount() is returning the withFirebase HOCcomponent, not the <Mount> component that you expect. what you need to do is "find" the contained component. In your example I think myContainedComponent = wrapper.find('Mount') would return the component that you could then do console.log(myContainedComponet.props); There are a lot of answers to similar questions about using Enzyme to test HOC and their enclosed components. I am using React 17 which is not supported by Enzyme.mount() so I have to use shallow. Again there are answers related to doing wrapper = shallow(shallow(<BurgerBuilder/>).get(0)); but these don't work for my setup either.
What is working for me is:
wrapper = shallow(<BurgerBuilder/>);
instance = wrapper.find('BurgerBuilder').dive().instance();
jest.spyOn(instance, 'addIngredientHandler');
NOTE: this is the export for the BurgerBuilder component.
export default withErrorHandler(BurgerBuilder, axiosOrders);
In this example, instance holds the class instance of the contained component rather than the HOC, withErrorHandler.
One of the interesting things about my example is that 'addIngredientHandler' is an arrow function in my class. There are other threads that talk about the complexities of testing class member arrow functions in React. (BTW, you do not need to do instance.forceUpdate(); )
In the interest of full disclosure, I am building my testing skills as I learn React. The components I am testing were developed while running through the Udemy course: React - The Complete Guide (incl Hooks, React Router, Redux)

Type of Axios mock using jest typescript

I have the following method in a class:
import axios from 'axios'
public async getData() {
const resp = await axios.get(Endpoints.DATA.URL)
return resp.data
}
Then I am trying to set up a Jest test that does this:
jest.mock('axios')
it('make api call to get data', () => {
component.getData()
expect(axios.get).toHaveBeenCalledWith(Endpoints.DATA.URL)
})
The problem is that because I am not mocking the return value, then it gives an error for resp.data because I'm calling data on null or undefined object. I spent at least 2 hours trying various ways to get this working but I can't find a way such that I can mock axios.get with some return value.
Jest's documentation uses JavaScript so they give this example axios.get.mockResolvedValue(resp) but I can't call mockResolvedValue because that method does not exist on axios.get in TypeScript.
Also, if you know other good testing library for React other than Jest that does this stuff easily for TypeScript, feel free to share.
In start of file:
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
Now you can use it as usual mock:
mockedAxios.get.mockRejectedValue('Network error: Something went wrong');
mockedAxios.get.mockResolvedValue({ data: {} });
If you want to use jest.mock with "no-any" try this:
import axios, { AxiosStatic } from 'axios'
interface AxiosMock extends AxiosStatic {
mockResolvedValue: Function
mockRejectedValue: Function
}
jest.mock('axios')
const mockAxios = axios as AxiosMock
it('make api call to get data', () => {
// call this first
mockAxios.mockResolvedValue(yourValue)
component.getData()
expect(mockAxios.get).toHaveBeenCalledWith(Endpoints.DATA.URL)
})
I kept running into is not a function issues. If the accepted answer doesn't work for you, then try importing axios with a capital A ie. Axios.
import Axios from 'axios';
jest.mock('Axios');
const mockedAxios = Axios as jest.Mocked<typeof Axios>;
This is what I personally always use.
import axios from 'axios';
jest.mock('axios')
it('...', () => {
(axios.get as jest.Mock).mockImplementationOnce(() => Promise.resolve({}));
// test here
expect(axios.get).toHaveBeenCalled()
}
As of Jest 24.9.0 here is how it works correctly typing both axios and Jest properties.
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks. As far as I seen non of the current answers enable that.
jest.MockedFunction
jest.MockedClass
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.MockedFunction<typeof axios>;
mockedAxios.mockResolvedValue({ status: 200, data: 'mockResponse' });
// Or:
(mockedAxios.get as jest.MockedFunction<typeof mockedAxios.get>).mockResolvedValue('mockResponse');
As you can see, you can either manually cast what you need or you'll need something to traverse all axios properties/methods to type everything.
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
import axios from 'axios';
jest.mock('axios');
const mockedAxios = jest.mocked(axios, true);
mockedAxios.mockImplementation() // correctly typed
mockedAxios.get.mockImplementation() // also correctly typed
but I can't call mockResolvedValue because that method does not exist on axios.get in TypeScript
You can use an assertion:
(axios.get as any).mockResolvedValue(resp)
I found a neat solution using the sinon library npm install sinon #types/sinon --save-dev.
Then the testing code becomes:
let component: Component
let axiosStub: SinonStub
beforeAll(() => {
component = new Component({})
axiosStub = sinon.stub(axios, 'get')
})
afterAll(() => {
axiosStub.restore()
})
it('make api call to get data', async () => {
// set up behavior
axiosStub.withArgs(Endpoints.DATA.URL).returns({data: []})
// method under test
const res = await component.getData()
// assertions
expect(res).toEqual([])
})
Another option is to use jest.spyOn:
import axios from "axios";
jest.spyOn(axios, "get").mockImplementation(() => Promise.resolve({data: []}));
This also gives you the benefit of having a mocked method that you can test against, for example:
import axios from "axios";
// ...
const mockedGet = jest
.spyOn(axios, "get")
.mockImplementation(() => Promise.resolve({data: []}));
// ...
expect(mockedGet).toBeCalledWith('https://example.api?q=abc&k=123');

Unit Testing dynamically imported React Component

I have a very simple React component that uses react-loadable to dynamically import another component. The code looks something akin to the following:
import React from 'react';
import Loadable from 'react-loadable';
import LoaderComponent from 'path/to/LoaderComponent';
export default outerLoadableComponent = Loadable({
loader: () => import('path/to/innerComponent'),
loading() {
return <LoaderComponent />
}
});
I am attempting to test this component by using Enzyme to mount outerLoadableComponent, which creates a wrapper around outerLoadableComponent where I can see that the LoaderComponent wrapping it has the loadingState set to true. However, I am stuck at the point where the inner import does not resolve. It seems to be a promise that would only resolve should the import actually go through, however even with some timeouts, it does not work:
const expect = chai.expect;
chai.use(sinonChai);
it('should render the loading state, and innerComponent', (done) => {
const wrapper = mount(
<outerLoadableComponent />
);
expect(wrapper.loading).to.be.true;
setTimeout(() => {
expect(wrapper.loading).to.be.false;
expect(wrapper.loaded).to.exist; // loaded state returns a function
expect(wrapper.find(innerComponent)).to.exist;
done();
}, 500);
});
My babel-rc has dynamic-import-node so running this outside of the test works all fine. But there seems to be no clear/documented way of mocking (with sinon) the results of an import promise. Any ideas?

Resources