I have a custom hook APIGateway calling another custom hook http. I'd like to mock the promise function sendHttpRequest to test sendAPIRequest. With this code, I am getting "Rejected to value: [TypeError: Cannot read property 'then' of undefined]"
I am trying to avoid any __mock__ files. If I mock axios, apiGateway.test passes.
How can I mock a function sendHttpRequest on the default export of useHttp?
http.js
import { useCallback } from 'react';
import axios from 'axios';
const useHttp = () => {
const sendRequest = useCallback((url, method, body) => {
return new Promise((resolve, reject) => {
axios({ method: method, url: url, data: body, config: { crossDomain: true } })
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
}, []);
return {
sendHttpRequest: sendRequest,
};
};
export default useHttp;
apiGateway.js
import { useCallback } from 'react';
import useHttp from '../abstract/http';
import configuration from '../../endpoints';
const useApiGateway = () => {
const { sendHttpRequest } = useHttp();
const apiGatewayBaseUrl = configuration.API_GATEWAY_BASE_URL;
const apiGatewayPath = configuration.LAMBDA_USER_ENDPOINT;
const sendRequest = useCallback((body) => {
return new Promise((resolve, reject) => {
sendHttpRequest(apiGatewayBaseUrl + apiGatewayPath, 'get', body)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}, []);
return {
sendApiRequest: sendRequest,
};
};
export default useApiGateway;
apiGateway.test.js
import React from 'react';
import { act, renderHook } from '#testing-library/react-hooks';
import useApiGateway from './apiGateway';
import useHttp from '../abstract/http';
jest.mock('../abstract/http', () => jest.fn());
describe('hook/aws/apiGateway', () => {
let result;
beforeEach(() => {});
it('should send GET request with no error', () => {
//TODO mock http instead of axios
let response = { data: '<html>Hello</html>' };
useHttp.mockImplementation(() => ({
sendHttpRequest: jest.fn(() => {}),
}));
let { sendHttpRequest } = useHttp();
sendHttpRequest.mockResolvedValue(
new Promise((resolve, reject) => {
resolve(response);
})
);
result = renderHook(() => useApiGateway()).result;
console.log(useHttp());
act(() => {
return expect(result.current.sendApiRequest({})).resolves.toEqual(response.data);
});
});
});
full error
Error: expect(received).resolves.toEqual()
Received promise rejected instead of resolved
Rejected to value: [TypeError: Cannot read property 'then' of undefined]
at expect (.../node_modules/expect/build/index.js:138:15)
at .../apiGateway.test.js:29:11
your mock should return a promise (rather than attempting to mock out the promise lib)
example:
function myMockRequest() {
return Promise.resolve({ mockResponse });
}
Related
I have been trying to implement some tests on my project, but I got some blockers.
This error:
Unable to find an element with the text: C-3PO/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
I don't know how to fix it. Should I use another query or I am missing something?
import { render, screen } from "#testing-library/react";
import CharacterList from "./index";
import { rest } from "msw";
import { setupServer } from "msw/node";
const server = setupServer(
rest.get("https://swapi.dev/api/people/", (req, res, ctx) => {
return res(
ctx.json({ results: [{ name: "Luke Skywalker", gender: "male" }] })
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe("render characters", () => {
it("should render character C-3PO when get api response", async () => {
render(<CharacterList />);
const character = await screen.findByText("C-3PO/i");
expect(character).toBeInTheDocument();
});
});
and my component:
import { NavLink } from "react-router-dom";
import { useEffect, useState } from "react";
export default function CharactersList() {
const [data, setData] = useState(undefined);
const [home, setHome] = useState(undefined);
useEffect(() => {
fetch("https://swapi.dev/api/people/")
.then((response) => {
if (response) {
return response.json();
} else {
return Promise.reject(response);
}
})
.then((data) => {
setData(data);
});
}, []);
if (data) {
return data.results.map((item) => {
const id = item.url.slice(29);
return (
<>
<NavLink to={`/character/${id}`}>{item.name}</NavLink>
<p>Gender: {item.gender}</p>
<p>Home planet: {item.homeworld}</p>
</>
);
});
} else {
return <p>Loading...</p>;
}
}
Please Add screen.debug() to see your actual screen, from that you can consider which get method will work to you.
I think the problem is you don't have C-3PO/i text in the DOM
describe("render characters", () => {
it("should render character C-3PO when get api response", async () => {
render(<CharacterList />);
screen.debug(); <------- ADD THIS
const character = await screen.findByText("C-3PO/i");
expect(character).toBeInTheDocument();
});
});
I am pretty new to testing React-Redux and I would like to test my loadUser-action which uses redux-thunk and calls an end point which has an auth middle ware. Here is code I would like to test:
export const loadUser = () => (dispatch, getState) => {
dispatch({ type: USER_LOADING });
axios
.get('/auth/user', tokenConfig(getState))
.then((res) =>
dispatch({
type: USER_LOADED,
payload: res.data,
})
)
.catch((err) => {
console.log(err);
dispatch({
type: LOADING_FAILURE,
});
});
};
export const tokenConfig = (getState) => {
const token = getState().auth.token;
const config = {
headers: {
'Content-type': 'application/json',
},
};
if (token) {
config.headers['x-auth-token'] = token;
}
console.log('CONFIG', config);
return config;
};
And this is my test this far:
import { mockStore } from '../../test/utils/mockStore';
import { USER_LOADED } from '../types/authTypes';
import { loadUser } from './authActions';
describe('loadUser', () => {
fit('gets user', async () => {
const store = mockStore();
const tokenConfig = jest.fn();
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: USER_LOADED, meta: {} });
});
});
The tokenConfig function must be called in a different way. I can't figure out how!
I would mock axios because you don't want to be doing actual API calls when running unit tests because it would use resources on your server. Also by mocking axios, you don't have to mock tokenConfig.
This is how I have done it on a personal project of mine:
import { mockStore } from '../../test/utils/mockStore';
import { USER_LOADED, LOADING_FAILURE } from '../types/authTypes';
import { loadUser } from './authActions';
import axios from 'axios';
jest.mock('axios'); // mock axios library
describe('loadUser', () => {
fit('gets user', async () => {
const store = mockStore();
axios.get.mockImplementationOnce(() => Promise.resolve({ data: {} })); // mock resolve success
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: USER_LOADED, payload: {} });
});
it('handles api failure', () => {
const store = mockStore();
axios.get.mockImplementationOnce(() => Promise.reject('Error')); // mock error
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: LOADING_FAILURE });
});
});
I'm using axios in React and I'm having problems with testing
This is what I've tried so far in my test.js file:
jest.mock('axios');
describe('RssFeed', () => {
let component;
let data;
beforeEach( () => {
data = data {
data: {...}
};
}
test('fetches data successfully', (done) => {
axios.get.mockResolvedValue(data);
setTimeout(() => {
expect(axios).toHaveBeenCalled();
done();
}, 500);
});
});
This is how I have the axios.get setup in my component:
const [feedBody, setFeedBody] = useState([]);
const apiUrl = 'apiUrl';
useEffect( () => {
axios.get(apiUrl)
.then((response) => (response.data))
.then((data) => {
setFeedBody(data);
})
.catch((err) => console.log(err));
}, []);
After I run my tests, I get:
Error: Uncaught [Error: expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0]
Thanks in advance
I suggest using axios-mock-adapter, it's very handy:
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
const resp = 'success';
const testInput = 'testInput';
mock.onGet().reply(200, resp);
// expect feedBody to be 'success'
mock.reset();
In my react application I have an async api call done with axios. And that api call does accept a custom callback.
I am able to test the axios api call using Jest + Enzyme. But not able to test the custom callback method.
Note: I have mocked my axios module.
src/mocks/axios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} }))
}
auth.api.js
import Axios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { setAuthToken } from '../actions/auth/auth.action';
export const saveUsers = (user, dispatch) => {
const URL = `${AUTH_SERVER_URL}/auth/register`;
Axios.post(URL, user)
.then(response => {
const { data } = response;
const token = {
accessToken: data.access_token,
};
return token;
})
.then(token => dispatch(setAuthToken(token)))
.catch(error => {
if (error.response) {
console.error(error.response.data.message);
}
})
}
And here is my test code.
spec.js
import mockAxios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { saveUsers } from './auth.api';
import { setAuthToken } from '../actions/auth/auth.action';
describe('Authentication API', () => {
it('saveUsers', () => {
const user = { x: 'test' }
const dispatch = jest.fn(); // need to test this dispatch function gets called or not
const response = {
data: {
access_token: 'access_token',
}
};
const expectedToken = {
accessToken: 'access_token',
};
mockAxios.post.mockImplementationOnce(() => Promise.resolve(response));
saveUsers(user, dispatch);
const url = `${AUTH_SERVER_URL}/auth/register`;
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(url, user);
console.log(dispatch.mock.calls);
expect(dispatch).toHaveBeenCalledTimes(1); // failed
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken)); // failed
});
})
Please help me in this
Try to install this package flush-promises.
Then import it in your test file
import flushPromises from 'flush-promises';
And add it before your assertions.
...
await flushPromises();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken));
And here add async.
it('saveUsers', async () => {
But I'm not sure if it will help.
Thanks to #Jakub Janik for his answer.
Bellow is my answer without using flush-promise package. But it is using the concept behind flush-promise.
import mockAxios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { saveUsers } from './auth.api';
import { setAuthToken } from '../actions/auth/auth.action';
// A helper function can turn that into a promise itself so you don't need to deal with the done callback.
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
describe('Authentication API', () => {
it('saveUsers', async () => {
const user = { x: 'test' }
const dispatch = jest.fn(); // need to test this dispatch function gets called or not
const response = {
data: {
access_token: 'access_token',
}
};
const expectedToken = {
accessToken: 'access_token',
};
mockAxios.post.mockImplementationOnce(() => Promise.resolve(response));
saveUsers(user, dispatch);
const url = `${AUTH_SERVER_URL}/auth/register`;
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(url, user);
await flushPromises(); // Magic happens here
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken));
});
})
I have an action that I want to test in my React/Redux app:
export const SET_SUBSCRIBED = 'SET_SUBSCRIBED'
export const setSubscribed = (subscribed) => {
// return {
// type: SET_SUBSCRIBED,
// subscribed: subscribed
// }
return function(dispatch) {
var url = "https://api.github.com/users/1/repos";
return fetch(url)
.then(function(result) {
if (result.status === 200) {
dispatch({
type: SET_SUBSCRIBED,
subscribed: subscribed
})
return result
}
return 'failed' //todo
})
}
}
Here is my test:
import { setSubscribed } from '../actions/subscriptions'
import nock from 'nock'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../actions/subscriptions'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Action::Subscriptions', () => {
describe('#setSubscribed()', () => {
afterEach(() => {
nock.cleanAll()
})
describe('when subscribed is true', () => {
beforeEach(() =>{
nock('https://api.github.com/')
.get('/users/1/repos')
.reply(200, 'yes')
})
it('returns SET_SUBSCRIBED type and subscribed true', () => {
const store = mockStore({ subscribed: false })
return store.dispatch(actions.setSubscribed(true))
.then(() => {
expect(store.getActions()).toEqual([{type: 'SET_SUBSCRIBED', subscribed: true}])
})
})
})
})
})
When I run the test, I keep getting the error:
TypeError: Network request failed
It looks like my test isn't too happy with nock.
What am I doing incorrectly and how do I successfully stub the fetch() request?