Mock inner axios.create() - javascript

I'm using jest and axios-mock-adapter to test axios API calls in redux async action creators.
I can't make them work when I'm using a axios instance that was created with axios.create() as such:
import axios from 'axios';
const { REACT_APP_BASE_URL } = process.env;
export const ajax = axios.create({
baseURL: REACT_APP_BASE_URL,
});
which I would consume it in my async action creator like:
import { ajax } from '../../api/Ajax'
export function reportGet(data) {
return async (dispatch, getState) => {
dispatch({ type: REQUEST_TRANSACTION_DATA })
try {
const result = await ajax.post(
END_POINT_MERCHANT_TRANSACTIONS_GET,
data,
)
dispatch({ type: RECEIVE_TRANSACTION_DATA, data: result.data })
return result.data
} catch (e) {
throw new Error(e);
}
}
}
Here is my test file:
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
let mock = new MockAdapter(axios)
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
And I only get one action Received: [{"type": "REQUEST_TRANSACTION_DATA"}] because there was an error with the ajax.post.
I have tried many ways to mock the axios.create to no avail without really knowing what I'm doing..Any Help is appreciated.

OK I got it. Here is how I fixed it! I ended up doing without any mocking libraries for axios!
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})

If you need to create Jest test which mocks the axios with create in a specific test (and don't need the mock axios for all test cases, as mentioned in other answers) you could also use:
const axios = require("axios");
jest.mock("axios");
beforeAll(() => {
axios.create.mockReturnThis();
});
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Here is the link to the same example of Axios mocking in Jest without create. The difference is to add axios.create.mockReturnThis()

here is my mock for axios
export default {
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
create: jest.fn(function () {
return {
interceptors:{
request : {
use: jest.fn(() => Promise.resolve({ data: {} })),
}
},
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
}
}),
};

In your mockAdapter, you're mocking the wrong instance. You should have mocked ajax instead. like this, const mock = MockAdapter(ajax)
This is because you are now not mocking the axios instance but rather the ajax because it's the one you're using to send the request, ie, you created an axios instance called ajax when you did export const ajax = axios.create...so since you're doing const result = await ajax.post in your code, its that ajax instance of axios that should be mocked, not axios in that case.

I have another solution.
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
// import axios from 'axios'
import { ajax } from '../../api/Ajax' // axios instance
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
// let mock = new MockAdapter(axios)
let mock = new MockAdapter(ajax) // this here need to mock axios instance
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})

another method: add this file to src/__mocks__ folder
import { AxiosStatic } from 'axios';
const axiosMock = jest.createMockFromModule<AxiosStatic>('axios');
axiosMock.create = jest.fn(() => axiosMock);
export default axiosMock;

The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Related

next.js getStaticProps Serialize issue

I'm using next.js for a project where axios fetch in getStaticProps doesnot seem to work even though the URL is serialised in configuration.I tried serializing again by passing the response to JSON.parse but still cant find a solution.
import axios from "axios";
import Qs from "qs";
My axios config code below:
const axiosTmdbApi = axios.create({
baseURL: "https://api.themoviedb.org/3",
headers: { "content-Type": "application/json/" },
paramsSerializer: {
serialize: (params) =>
Qs.stringify({ ...params, api_key: apiKey }, { arrayFormat: "brackets" }),
},
});```
**My category which is passed as a parameter to invoke getTvList or getMovieList data below:**
import axiosTmdbApi from "./axiosTmdbApi";
export const category = {
movie: "movie",
tv: "tv",
};
export const type = {
top_rated: "top_rated",
popular: "popular",
};
const tmdbApi = {
getTvList: (tvType, params) => {
const url = "tv/" + type[tvType];
return axiosTmdbApi.get(url, params);
},
getMovielist: (movieType, params) => {
const url = "movie/" + type[movieType];
return axiosTmdbApi.get(url, params);
},
};
export default tmdbApi;```
Using getStaticProps to fetch my API
import tmdbApi from "../../api/tmdbApi";
import { type, category } from "../../api/tmdbApi";
const Movies = ({ data }) => {
console.log(data);
return (
<>
<h1 className="bg-success">Movies</h1>
</>
);
};
export default Movies;
export async function getStaticProps() {
let params = {};
let response;
response = await tmdbApi.getMovielist(type.popular, {
params,
});
const data = JSON.parse(JSON.stringify(response));
return {
props: { data },
};
}```
**Error :index.js?46cb:602 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'TLSSocket'
--- property '_httpMessage' closes the circle **
Try adding console.log and see what values are being handled at each stage. Instead of const data = JSON.parse(JSON.stringify(response)), you should be doing const data = response.data.
and change return statement to
return {
props: { data: data || [] },
};

How to mock Cognito response in Typescript

After defining class CognitoPool saving it as cognitoPool.ts script:
const AWS = require('aws-sdk');
import { CognitoIdentityServiceProvider } from 'aws-sdk';
import {ListUsersRequest, ListUsersResponse} from 'aws-sdk/clients/cognitoidentityserviceprovider';
export class CognitoPool {
private identityService: CognitoIdentityServiceProvider;
constructor(identityService: CognitoIdentityServiceProvider) {
this.identityService = identityService;
}
async listCognitoUsers(poolID: string, sub: string): Promise<ListUsersResponse> {
let params = {
UserPoolId: poolID,
Filter: `sub="${sub}"`
} as ListUsersRequest;
let res: ListUsersResponse = await this.identityService.listUsers(params).promise();
return res;
}
}
export default new CognitoPool(new AWS.CognitoIdentityServiceProvider());
I go ahead and write a test script:
const AWS = require('aws-sdk');
import sinon, { stubObject } from 'ts-sinon'
import { CognitoIdentityServiceProvider, AWSError } from 'aws-sdk';
import { PromiseResult } from 'aws-sdk/lib/request';
import { CognitoPool } from './cognitoPool';
describe('Testing', () => {
const identityService = new AWS.CognitoIdentityServiceProvider();
const stub = stubObject(identityService);
const cognitoPool = new CognitoPool(stub);
it('Test 01', async () => {
let mockData = {
Users: []
} as unknown as PromiseResult<any, AWSError>;
stub.listUsers.returns(mockData);
let result = await cognitoPool.listCognitoUsers('poolId-123', 'sub-123');
})
})
A mockData is to be returned by identityService.listUsers() as as PromiseResult:
let mockData = {
Users: []
} as unknown as PromiseResult<any, AWSError>;
But a test script runs with an error:
TypeError: this.identityService.listUsers(...).promise is not a function
Is there a way to avoid this error?
PromiseResult is an object that includes .promise as a function. Then, when you want to mock a function to return a PromiseResult, the mock data should be an object like PromiseResult.
In your case, mockData should be:
const mockData = {
promise: () => Promise.resolve({ Users: [] }),
} as unknown as PromiseResult<any, AWSError>;

Error testing external API function with jest (react-query: useQuery)

When using Jest to test a function I have that makes a call to external API I get an error about only being able to use a hooks inside of a functional component.
My function(useGetGophys) uses useQuery from react-query which is the hook.
I would like to be able to test the useGetGophy with jest please?
I am mocking the actual fetch request as can be seen in the test file code below.
useGetGophy.js
import { useMemo } from 'react'
import { useQuery } from 'react-query'
import urlGenerator from "../utils/urlGenerator"
export default function useGetGophys({ query, limit }) {
const url = urlGenerator({ query, limit })
const { data, status } = useQuery(["gophys", { url }], async () => {
const res = await fetch(url)
return res.json()
})
return {
status,
data,
}
}
Test file
useGetGophy.test.js
import useGetGophys from '../services/useGetGophys'
import { renderHook } from '#testing-library/react-hooks'
import { QueryClient, QueryClientProvider } from "react-query"
const desiredDataStructure = [{
id: expect.any(String),
images: {
fixed_width_downsampled: {
url: expect.any(String),
width: expect.any(String),
height: expect.any(String),
},
},
embed_url: expect.any(String),
bitly_gif_url: expect.any(String),
url: expect.any(String),
title: expect.any(String),
}]
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(desiredDataStructure)
})
)
describe('getGetGophy - ', () => {
test('returns correctly structured data', async () => {
const gophys = useGetGophys('https://api.giphy.com/v1/gifs/trending?q=daniel&api_key=00000000&limit=15&rating=g')
expect(gophys).toBe(desiredDataStructure)
})
})
You would need to render your hook using testing-library/react-hooks. As long as you are returning an object, check for result.current.data, something like:
import { renderHook } from '#testing-library/react-hooks';
test('returns correctly structured data', () => {
const { result } = renderHook(() => useGetGophys('yourUrl'));
expect(result.current.data).toEqual(desiredDataStructure);
});

Testing redux async method with moxios

I'm new to redux and pulling out my hair trying to get a basic test to work with redux and moxios.
API is just axios, with some custom headers set.
I get an error on my post method:
TypeError: Cannot read property 'then' of undefined
my method:
const login = ({username, password}) => (dispatch) => {
dispatch(actions.loginRequested());
return API.post(`curavi/v2/authentication`, {username, password})
.then(response => dispatch(actions.loginSuccess(response.data.payload)))
.catch((error) => errorHandler(dispatch, error.response));
};
My Test case:
describe('login', () => {
beforeEach(function () {
// import and pass your custom axios instance to this method
moxios.install(API)
});
afterEach(function () {
// import and pass your custom axios instance to this method
moxios.uninstall(API)
});
test('calls loginSuccess when the response is successful', () => {
const store = mockStore();
const mockData = {
data: { payload: 'yay' }
};
moxios.wait(() => {
const request = API.requests.mostRecent();
request.respondWith({
status: 200,
response: mockData
});
});
const expectededActions = [
{type: types.LOGIN_REQUESTED},
{type: types.LOGIN_SUCCESS, payload: 'yay'}
];
actions.loginRequested.mockReturnValue({type: types.LOGIN_REQUESTED});
actions.loginSuccess.mockReturnValue({type: types.LOGIN_SUCCESS, payload: 'yay'});
actions.loginFail.mockReturnValue({type: types.LOGIN_FAIL, message: 'boo'});
return store.dispatch(operations.login({username: 'theuser', password: 'thepassword'}))
.then(() => {
expect(store.getActions()).toEqual(expectededActions);
expect(API.post).toHaveBeenCalledWith('curavi/v2/authentication',
{username: 'theuser', password: 'thepassword'});
});
})
});
Are you sure you get a TypeError in login as you suggest? It doesn't make sense; you'd get that error if API were not an axios instance, in which case API.post() could return undefined. On the other hand, your test won't work for 2 reasons:
You need to replace API.requests.mostRecent() with moxios.requests.mostRecent().
The function you have inside moxios' await won't execute for 0.5 secs, see here. If the return statement in your test were to be reached before then, your test would simply return a promise. You could do the following instead:
test('...', async () => {
// ...
const result = await store.dispatch(
operations.login({
username: 'theuser',
password: 'thepassword',
})
);
expect(store.getActions()).toEqual(expectededActions);
expect(API.post).toHaveBeenCalledWith(/* ... */);
});
You should also make sure to set up the store correctly:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
// use your store inside your tests
const store = mockStore();

How can i test an API call in vuejs using jest?

im having this method in my component that makes an API call with axios, I checked the docs on how to test it but I cant seem to figure out how to do so. Any help would be appreciated.
loadContents() {
axios.get('/vue_api/content/' + this.slug).then(response => {
this.page_data = response.data.merchandising_page
}).catch(error => {
console.log(error)
})
},
You could use moxios or axios-mock-adapter to automatically mock Axios requests. I prefer the latter for developer ergonomics.
Consider this UserList component that uses Axios to fetch user data:
// UserList.vue
export default {
data() {
return {
users: []
};
},
methods: {
async loadUsers() {
const { data } = await axios.get("https://api/users");
this.users = data;
}
}
};
With axios-mock-adapter, the related test stubs the Axios GET requests to the API URL, returning mock data instead:
import axios from "axios";
const MockAdapter = require("axios-mock-adapter");
const mock = new MockAdapter(axios);
import { shallowMount } from "#vue/test-utils";
import UserList from "#/components/UserList";
describe("UserList", () => {
afterAll(() => mock.restore());
beforeEach(() => mock.reset());
it("loads users", async () => {
mock
.onGet("https://api/users")
.reply(200, [{ name: "foo" }, { name: "bar" }, { name: "baz" }]);
const wrapper = shallowMount(UserList);
await wrapper.vm.loadUsers();
const listItems = wrapper.findAll("li");
expect(listItems).toHaveLength(3);
});
});
demo

Categories

Resources