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

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

Related

How to clear/reset mocks in Vitest

I have a simple composable useRoles which I need to test
import { computed } from "vue";
import { useStore } from "./store";
export default function useRoles() {
const store = useStore();
const isLearner = computed(() => store.state.profile.currentRole === "learner");
return {
isLearner
};
}
My approach of testing it is the following
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => { // works
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
}));
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => { //fails
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
}));
const { isLearner } = useRoles(); // Values are from prev mock
expect(isLearner.value).toBeFalsy();
});
});
And useStore is just a simple function that I intended to mock
export function useStore() {
return {/**/};
}
The first test runs successfully, it has all the mock values I implemented but the problem is that it's not resetting for each test (not resetting at all). The second test has the old values from the previous mock.
I have used
vi.clearAllMocks();
vi.resetAllMocks();
but for some reason clear or reset is not happening.
How can I clear vi.mock value for each test?
Solution
As it turned out I should not be called vi.mock multiple times. That was the main mistake
Substitutes all imported modules from provided path with another module. You can use configured Vite aliases inside a path. The call to vi.mock is hoisted, so it doesn't matter where you call it. It will always be executed before all imports.
Vitest statically analyzes your files to hoist vi.mock. It means that you cannot use vi that was not imported directly from vitest package (for example, from some utility file)
Docs
My fixed solution is below.
import useRoles from "./useRoles";
import { useStore } from "./store"; // Required the mock to work
vi.mock("./store");
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => {
// #ts-ignore it is a mocked instance so we can use any vitest methods
useStore.mockReturnValue({
state: {
profile: {
currentRole: "learner",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => {
// You need to use either #ts-ignore or typecast it
// as following (<MockedFunction<typeof useStore>>useStore)
// since original function has different type but vitest mock transformed it
(<MockedFunction<typeof useStore>>useStore).mockReturnValue({
state: {
profile: {
currentRole: "admin",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeFalsy();
});
});
vitest = v0.23.0
I ran into the same issue and was able to find a workaround. In your case it should look like this:
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
vi.mock("./store");
describe("useRoles", () => {
it("should verify values when is:Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
});
const { isLearner } = useRoles(); // Value is from current mock
expect(isLearner.value).toBeFalsy();
});
});

How to fix Cannot read property ‘$api’ of undefined in vue-test-utils

I have a button and will make an API call in the onclick function. The api .js is under the /api. It works well when I manually test it. However, I failed the unit test of the component because somehow it cannot find the api. I'm pretty new to the vuex test and not sure what is the correct way to mock the api call here.
Here is the click function in the component
methods: {
...mapActions([
'setNewField',
]),
async updateFields () {
const newField = {
value: this.fieldName,
text: this.fieldName,
}
await this.setNewField(newField)
}
}
Here is the related actions part in vue store. It calls the api and update the state
export const actions = {
async setNewField ({ commit }, field) {
await this.$app.$api.putField(field.value)
commit('SET_NEW_FIELD', field)
}
}
Here is the tester
describe('Selector', () => {
let store
let wrapper
let $app
const setNewFieldFunction = jest.fn()
beforeEach(() => {
jest.resetAllMocks()
$app = {
$api : {
putField: jest.fn()
}
}
store = new Vuex.Store({
modules: {
options: cloneDeep(optionsModule),
headers: {
actions: {
setNewField: setNewFieldFunction,
}
}
}
})
wrapper = shallowMount(Selector, { store, mocks: { $app } })
})
it('should add a field', async () => {
// add a field
expect(addButton.vm.disabled).toEqual(true)
fieldName.vm.$emit('input', 'TEST Field 1')
expect(addButton.vm.disabled).toEqual(false)
addButton.vm.$emit('click')
treeview.vm.$emit('input', ['TEST Field 1'])
await flushPromises()
expect(wrapper.vm.fieldName).toEqual('TEST Field 1')
expect(setNewFieldFunction).toHaveBeenCalled()
})
})
Thanks for any help!!

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

Mock inner axios.create()

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

Categories

Resources