I am using Jest and moxios to write a test for my async function:
export function getData(id) {
return dispatch => {
return axios({
method: "get",
url: `${'url'}/id`
})
.then(response => {
dispatch(setData(response.data));
})
.catch(() => alert('Could not fetch data');
};
}
Test:
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import moxios from "moxios";
import getData from '../redux/getData';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({});
describe('Test fetch data', () => {
beforeEach(function() {
moxios.install();
store.clearActions();
});
afterEach(function() {
moxios.uninstall();
});
it('should fetch data and set it', () => {
const data = [{ name: 'John', profession: 'developer'}];
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: data
});
const expectedActions = [setData(data)];
return store.dispatch(getData()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
})
})
My test is passing, but when I check the code coverage report generated by Jest, it shows that the then block of getData was not covered/called. How can I fix this?
moxios.wait return Promise your test function return before running except functions.
you need to use done callback in your test function
it('should fetch data and set it', (done) => {
const data = [{
name: 'John',
profession: 'developer'
}];
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: data
});
const expectedActions = [setData(data)];
store.dispatch(getData()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
done();
});
});
});
Related
I mocked axios during the tests, which is been called in one of my functions, but it's returning undefined instead of what I setted to return.
I already tried several other solutions I found online, but none of them worked.
Function where axios is called:
static getWeather() {
const apiKey = ""
const apiRequest = "https://api.openweathermap.org/data/2.5/weather?q=London&appid=" + apiKey
return axios.get(apiRequest);
}
}
export default WeatherService
src/__mocks__/axios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
}
Test
import axios from 'axios';
import WeatherService from '../service/WeatherService';
describe('WeatherService', () => {
test('Should mock axios', () => {
const apiKey = ""
axios.get.mockImplementationOnce( () => {
Promise.resolve({ data: { response: true } })
});
const res = WeatherService.getWeather();
console.log(res);
expect(res.data.response).toBe(true);
expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith("https://api.openweathermap.org/data/2.5/weather?q=London&appid=" + apiKey)
})
})
I don't know if it changes anything but I'm using single-spa
Have you tried changing this:
axios.get.mockImplementationOnce( () => {
Promise.resolve({ data: { response: true } })
});
to this:
axios.get.mockImplementationOnce( () => Promise.resolve({ data: { response: true } }));
?
I am not very familier yet on how to write unit tests via moxios and your help would be very much appreciated.
My request is the following:
export const walletRequest = () => {
return AWAxiosInstance()
.get(`${AW_BASE_URL}/account/wallet`)
.then(response => {
if (response) {
return formatAccountDetails(response.data);
}
})
.catch(error => {
return Promise.reject('Error requesting data from Account & Wallet API', error)
})
}
So basically here in the above function I'm trying to retrieve some data via an axios instance.
My understanding is that moxios is being used to mock the axios instance, but I am not very sure how to write the unit test for the walletRequest() function.
What I've tried:
import moxios from 'moxios'
import { walletRequest } from "../balance";
import AWAxiosInstance from '../../../../core/aw-axios-instance'
const responseMock = { balance: 100 };
describe("services/balance2", () => {
beforeEach(() => {
moxios.install(AWAxiosInstance)
})
afterEach(() => {
moxios.uninstall(AWAxiosInstance)
})
it("should call the walletRequest and retrieve data", () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: {
responseMock
}
})
})
const response = walletRequest().response;
expect(response).toEqual(responseMock);
});
});
This doesn't work at this moment as the walletRequest() response is undefined.
What can I do?
Thank you in advance!
Solved this:
beforeEach(() => {
moxios.install(AWAxiosInstance)
formatAccountDetails.mockImplementation( () => responseMock)
})
afterEach(() => {
moxios.uninstall(AWAxiosInstance)
})
it("should return the data", async () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: {
responseMock
}
})
})
const response = await walletRequest();
expect(response).toEqual(responseMock);
});
it('should not recieve response when request is rejected', () => {
const errorResp = {
status: 400,
response: { message: 'invalid data',
data: 'invalid data' }
};
const response = walletRequest();
moxios.wait(async() => {
let request = moxios.requests.mostRecent();
request.reject(errorResp);
response.then((err) => {
expect(err).toBe('invalid data');
});
});
});
I implemented my own way to handle access/refresh token. Basically when accessToken is expired, it awaits the dispatch of another action and, if it is successful, it dispatch again itself. The code below explains it better:
export const refresh = () => async (dispatch) => {
dispatch({
type: REFRESH_USER_FETCHING,
});
try {
const user = await api.refresh();
dispatch({
type: REFRESH_USER_SUCCESS,
payload: user,
});
return history.push("/");
} catch (err) {
const { code } = err;
if (code !== "ACCESS_TOKEN_EXPIRED") {
dispatch({
type: REFRESH_USER_ERROR,
payload: err,
});
const pathsToRedirect = ["/signup"];
const {
location: { pathname },
} = history;
const path = pathsToRedirect.includes(pathname) ? pathname : "/login";
return history.push(path);
}
try {
await dispatch(refreshToken());
return dispatch(refresh());
} catch (subErr) {
dispatch({
type: REFRESH_USER_ERROR,
payload: err,
});
return history.push("/login");
}
}
};
export const refreshToken = () => async (dispatch) => {
dispatch({
type: REFRESH_TOKEN_FETCHING,
});
try {
await api.refreshToken();
dispatch({
type: REFRESH_TOKEN_SUCCESS,
});
} catch (err) {
dispatch({
type: REFRESH_TOKEN_ERROR,
payload: err,
});
}
};
the issue is that I am finding it really difficult to test with Jest. In fact, I have implemented this test:
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as actionCreators from "./actionCreators";
import * as actions from "./actions";
import api from "../../api";
jest.mock("../../api");
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("authentication actionCreators", () => {
it("runs refresh, both token expired, should match the whole flow", async () => {
api.refresh.mockRejectedValue({
code: "ACCESS_TOKEN_EXPIRED",
message: "jwt expired",
});
api.refreshToken.mockRejectedValue({
code: "REFRESH_TOKEN_EXPIRED",
message: "jwt expired",
});
const expectedActions = [
{ type: actions.REFRESH_USER_FETCHING },
{ type: actions.REFRESH_TOKEN_FETCHING },
{ type: actions.REFRESH_TOKEN_ERROR },
{ type: actions.REFRESH_USER_ERROR },
];
const store = mockStore({ auth: {} });
await store.dispatch(actionCreators.refresh());
expect(store.getActions()).toEqual(expectedActions);
});
});
but instead of completing, the test runs indefenitely. This issue is not happening when I am testing it manually, so I think there is something missing in Jest, so my question is: is there a way to test this recursive behaviour?
Thanks
The problem is await you use with dispatch, dispatch returns an action, not a Promise, use Promise.resolve instead.
My purpose is simply to test one function. I cannot figure out how to mock firebase properly. I try to keep the example with axios mocking from Jest docs. I have the following code:
MusicService.js
import { initializeApp } from "firebase/app";
import "firebase/database";
const firebase = initializeApp({
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
projectId: "<PROJECT_ID>",
storageBucket: "<BUCKET>.appspot.com",
messagingSenderId: "<SENDER_ID>",
});
export class MusicService {
static getAlbums() {
return firebase.database().ref("albums").once("value")
.then(snapshot => Object.values(snapshot.val()));
}
}
MusicService.test.js
import firebase from 'firebase/app';
import 'firebase/database';
import { MusicService } from './MusicService';
jest.mock('firebase/app');
jest.mock('firebase/database');
test("test", () => {
firebase.initializeApp.mockImplementation(() => {
database: jest.fn(() => {
return {
ref: jest.fn()
}
})
});
MusicService.getAlbums();
});
The problem is that I get the following error:
I tried to mock firebase.database.
test("test", () => {
firebase.mockImplementation(() => {
return {
database: {
}
}
});
MusicService.getAlbums();
});
But in this case I get the error that says:
TypeError: _app.default.mockImplementation is not a function.
I don't expect the working example will be given, but rather could you tell please, what exactly should I mock? The whole firebase library or maybe the part where my function starts - return firebase.database().
I have figured out. I should mock only those modules, a function I am going to test, depends on. For example, I want to test getAlbums function. It uses initializeApp function which is imported from firebase/app module in MusicService.js. So when initializeApp function is being called it should return an object containing database function which in turn returns an object with ref and once functions. Code:
MusicService.test.js.
import { MusicService } from "./FirebaseService";
jest.mock("firebase/app", () => {
const data = { name: "unnamed" };
const snapshot = { val: () => data };
return {
initializeApp: jest.fn().mockReturnValue({
database: jest.fn().mockReturnValue({
ref: jest.fn().mockReturnThis(),
once: jest.fn(() => Promise.resolve(snapshot))
})
})
};
});
test("getAlbums function returns an array", async () => {
const data = await MusicService.getAlbums();
expect(data.constructor).toEqual(Array);
});
this is my current mock implementation for firebase.js.
for me it is working fine.
const firebase = jest.genMockFromModule('firebase');
firebase.initializeApp = jest.fn();
const data = { name: 'data' };
const snapshot = { val: () => data, exportVal: () => data, exists: jest.fn(() => true) };
firebase.database = jest.fn().mockReturnValue({
ref: jest.fn().mockReturnThis(),
on: jest.fn((eventType, callback) => callback(snapshot)),
update: jest.fn(() => Promise.resolve(snapshot)),
remove: jest.fn(() => Promise.resolve()),
once: jest.fn(() => Promise.resolve(snapshot)),
});
firebase.auth = jest.fn().mockReturnValue({
currentUser: true,
signOut() {
return Promise.resolve();
},
signInWithEmailAndPassword(email, password) {
return new Promise((resolve, reject) => {
if (password === 'sign' || password === 'key') {
resolve({ name: 'user' });
}
reject(Error('sign in error '));
});
},
createUserWithEmailAndPassword(email, password) {
return new Promise((resolve, reject) => {
if (password === 'create' || password === 'key') {
resolve({ name: 'createUser' });
}
reject(Error('create user error '));
});
},
});
export default firebase;
I'm having trouble getting the correct output from an async redux action. I am using Jest, redux-mock-adapter, and thunk as the tools.
According to redux's documentation on testing async thunks (https://redux.js.org/docs/recipes/WritingTests.html#async-action-creators), my tests should be returning an array of two actions. However, my test is only returning the first action, and not the second one that should return on a successful fetch. I think I'm just missing something small here, but it has been bothersome to say the least.
Redux Action
export const getRemoveFileMetrics = cacheKey => dispatch => {
dispatch({ type: IS_FETCHING_DELETE_METRICS });
return axios
.get("GetRemoveFileMetrics", { params: { cacheKey } })
.then(response => dispatch({ type: GET_REMOVE_FILE_METRICS, payload: response.data }))
.catch(err => err);
};
Test
it("getRemoveFileMetrics() should dispatch GET_REMOVE_FILE_METRICS on successful fetch", () => {
const store = mockStore({});
const cacheKey = "abc123doremi";
const removeFileMetrics = {
cacheKey,
duplicateFileCount: 3,
uniqueFileCount: 12,
};
const expectedActions = [
{
type: MOA.IS_FETCHING_DELETE_METRICS,
},
{
type: MOA.GET_REMOVE_FILE_METRICS,
payload: removeFileMetrics,
}
];
mockRequest.onGet(`/GetRemoveFileMetrics?cacheKey=${cacheKey}`).reply(200, removeFileMetrics);
return store.dispatch(MOA.getRemoveFileMetrics(cacheKey)).then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedActions);
});
});
The Output
Expected value to equal:
[{ "type": "IS_FETCHING_DELETE_METRICS" }, { "payload": { "cacheKey": "abc123doremi", "duplicateFileCount": 3, "uniqueFileCount": 12 }, "type": "GET_REMOVE_FILE_METRICS" }]
Received:
[{ "type": "IS_FETCHING_DELETE_METRICS" }]
I am using jest-fetch-mock and no axios. The following is working for me with the actions. You could refactor to async await as first step. For me it only worked that way.
I am now trying to figure out how to test the side effect (showErrorAlert(jsonResponse);). If I mock out the showErrorAlert implementation at the top of the test file (commented out in my example) then I get the same problem just like you. Actions that uses fetch won't get triggered for some reason.
export const submitTeammateInvitation = (data) => {
const config = {
//.....
};
return async (dispatch) => {
dispatch(submitTeammateInvitationRequest());
try {
const response = await fetch(inviteTeammateEndpoint, config);
const jsonResponse = await response.json();
if (!response.ok) {
showErrorAlert(jsonResponse);
dispatch(submitTeammateInvitationError(jsonResponse));
throw new Error(response.statusText);
}
dispatch(submitTeammateInvitationSuccess());
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.log('Request failed', error);
}
}
};
};
test
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
// jest.mock('../../../../_helpers/alerts', ()=> ({ showAlertError: jest.fn() }));
const middlewares = [thunk];
const createMockStore = configureMockStore(middlewares);
......
it('dispatches the correct actions on a failed fetch request', () => {
fetch.mockResponse(
JSON.stringify(error),
{ status: 500, statusText: 'Internal Server Error' }
);
const store = createMockStore({});
const expectedActions = [
{
type: 'SUBMIT_TEAMMATE_INVITATION_REQUEST',
},
{
type: 'SUBMIT_TEAMMATE_INVITATION_FAILURE',
payload: { error }
}
];
return store.dispatch(submitTeammateInvitation(data))
.then(() => {
// expect(alerts.showAlertError).toBeCalled();
expect(store.getActions()).toEqual(expectedActions);
});
});