Testing redux async method with moxios - javascript

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

Related

I am using provide/inject to pass the data within components , I've this function using compute, I am trying to run and pass it's result in provide

I have 4 functions, for 1st three functions, I can send the data in provide. For 4th function(
getViewApplicationDetails
), I am trying to fetch api and get application name, now I want that in mounted because, I want the application name as soon as component is rendered so I am trying to execute it in mounted but when I call the it, it's giving me error. Initially application name is empty and it should have the current application name when I fetch the api, the same application name will be used in provide and then I can use that in inject and then in any other component.
import { computed, inject, onMounted, provide, reactive } from "vue";
export const initStore = () => {
onMounted(()=>{
this.getViewApplicationDetails()
});
// State
const state = reactive({
name: "Bob Day",
email: "bob#martianmovers.com",
applicationName: "",
breadcrumbsData: [
{
name: "Home",
text: 'Home',
disabled: false,
href: '/'
}
]
});
// Getters
const getUsername = computed(() => state.name);
const getEmail = computed(() => console.log("state.email",state.email));
const getBreadcrumbsData=computed(()=>state.breadcrumbsData)
console.log("state.applicationName",state.applicationName)
//this is the temporary function
const getApplicationName=computed(()=>state.applicationName)
const getViewApplicationDetails=computed(()=> {
var viewApplicationDetailsParams = {
applicationId: this.$route.query.applicationId,
applicationStatus:this.$route.query.appStatus,
authType: "api",
clientId: process.env.VUE_APP_EXTERNAL_API_CLIENT_ID,
clientSecret: process.env.VUE_APP_EXTERNAL_API_CLIENT_SECRET
};
axios({
method: "post",
url: process.env.VUE_APP_BLUJ_BACKEND_URL + "/viewapplicationDefinition",
data: viewApplicationDetailsParams,
headers: {
"content-type": "application/json",
},
})
.then((response) =>{
this.viewDefinitionResponse = response.data.Definitions;
let applicationName = viewDefinitionResponse.application_display_name.en;
console.log("tyfgyhkjlfhgjklnm",applicationName)
setApplicationName(applicationName)
})
.catch((error) => {
console.log("error", error);
});
});
getViewApplicationDetails()
// Mutations
const setUsername = (name) => {
state.name = name;
};
const setEmail = (email) => {
state.email = email;
};
const setBreadCrumbsData=(breadcrumbsData)=>{
state.breadcrumbsData=breadcrumbsData;
}
const setApplicationName=(appName)=>{
state.applicationName=appName
}
// Actions
const updateUsername = (name) => {
setUsername(name);
};
const updateEmail = (email) => {
setEmail(email);
};
provide("getUsername", getUsername);
provide("getEmail", getEmail);
provide("updateUsername", updateUsername);
provide("updateEmail", updateEmail);
provide("getViewApplicationDetails", getViewApplicationDetails);
provide("getApplicationName", getApplicationName);
provide("getBreadcrumbsData", getBreadcrumbsData);
};
export const useStore = () => ({
getUsername: inject("getUsername"),
getEmail: inject("getEmail"),
updateUsername: inject("updateUsername"),
updateEmail: inject("updateEmail"),
viewApplicationDetails: inject("getViewApplicationDetails"),
getBreadcrumbsData: inject("getBreadcrumbsData"),
getApplicationName: inject("getApplicationName")
});
This is the code snippet.
const getUsername = computed(() => state.name);
const getEmail = computed(() => console.log("state.email",state.email));
const getBreadcrumbsData=computed(()=>state.breadcrumbsData)
I am getting data for this, but for getViewApplicationDetails, it's not working. While hovering over rest of the functions, it is showing "const getUsername: ComputedRef", like this. But, for getViewApplicationDetails, it shows "const getViewApplicationDetails: ComputedRef", this. I think it is not taking it as function or something. Error image is in the link.enter image description here

How to pre-fetch data using prefetchQuery with React-Query

I am trying to pre-fetch data using react-query prefetchQuery. When I am inspecting browser DevTools network tab I can see that data that was requested for prefetchQuery is coming from the back-end but for some reason when I look into react-query DevTools it does generate the key in the cache but for some reason the Data is not there. Let me know what I am doing wrong.
import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import axios from 'axios';
const baseURL = process.env.api;
async function getSubCategoryListByCategoryId(id) {
// await new Promise((resolve) => setTimeout(resolve, 300));
console.log(`${baseURL}/category/subcategories/${id}`);
try {
const { data } = await axios.request({
baseURL,
url: `/category/subcategories/${id}`,
method: 'get',
});
console.log('data getSubCategoryListByCategoryId index: ', data);
return data;
} catch (error) {
console.log('getSubCategoryListByCategoryId error:', error);
}
}
// const initialState = {
// };
const ProductCreate = () => {
const [values, setValues] = useState(initialState);
const queryClient = useQueryClient();
const { data, isLoading, isError, error, isFetching } = useQuery(
'categoryList',
getPosts
);
const dataList = JSON.parse(data);
useEffect(() => {
setValues({ ...values, categories: dataList });
dataList.map((item) => {
console.log('useEffect values.categories item.id: ', item._id);
queryClient.prefetchQuery(
['subCategoryListByCategoryId', item._id],
getSubCategoryListByCategoryId(item._id)
);
});
}, []);
return <h1>Hello</h1>;
};
export default ProductCreate;
The second parameter to prefetchQuery expects a function that will fetch the data, similar to the queryFn passed to useQuery.
But here, you are invoking the function, thus passing the result of it into prefetchQuery:
getSubCategoryListByCategoryId(item._id)
if you want to do that, you can manually prime the query via queryClient.setQueryData, which accepts a key and the data for that key passed to it.
otherwise, the fix is probably just:
() => getSubCategoryListByCategoryId(item._id)

action creator does not return value to stream in marble test

I've got following Epic which works well in application, but I can't get my marble test working. I am calling action creator in map and it does return correct object into stream, but in the test I am getting empty stream back.
export const updateRemoteFieldEpic = action$ =>
action$.pipe(
ofType(UPDATE_REMOTE_FIELD),
filter(({ payload: { update = true } }) => update),
mergeMap(({ payload }) => {
const { orderId, fields } = payload;
const requiredFieldIds = [4, 12]; // 4 = Name, 12 = Client-lookup
const requestData = {
id: orderId,
customFields: fields
.map(field => {
return (!field.value && !requiredFieldIds.includes(field.id)) ||
field.value
? field
: null;
})
.filter(Boolean)
};
if (requestData.customFields.length > 0) {
return from(axios.post(`/customfields/${orderId}`, requestData)).pipe(
map(() => queueAlert("Draft Saved")),
catchError(err => {
const errorMessage =
err.response &&
err.response.data &&
err.response.data.validationResult
? err.response.data.validationResult[0]
: undefined;
return of(queueAlert(errorMessage));
})
);
}
return of();
})
);
On successfull response from server I am calling queueAlert action creator.
export const queueAlert = (
message,
position = {
vertical: "bottom",
horizontal: "center"
}
) => ({
type: QUEUE_ALERT,
payload: {
key: uniqueId(),
open: true,
message,
position
}
});
and here is my test case
describe("updateRemoteFieldEpic", () => {
const sandbox = sinon.createSandbox();
let scheduler;
beforeEach(() => {
scheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
});
afterEach(() => {
sandbox.restore();
});
it("should return success message", () => {
scheduler.run(ts => {
const inputM = "--a--";
const outputM = "--b--";
const values = {
a: updateRemoteField({
orderId: 1,
fields: [{ value: "test string", id: 20 }],
update: true
}),
b: queueAlert("Draft Saved")
};
const source = ActionsObservable.from(ts.cold(inputM, values));
const actual = updateRemoteFieldEpic(source);
const axiosStub = sandbox
.stub(axios, "post")
.returns([]);
ts.expectObservable(actual).toBe(outputM, values);
ts.flush();
expect(axiosStub.called).toBe(true);
});
});
});
output stream in actual returns empty array
I tried to return from map observable of the action creator which crashed application because action expected object.
By stubbing axios.post(...) as [], you get from([]) in the epic - an empty observable that doesn't emit any values. That's why your mergeMap is never called. You can fix this by using a single-element array as stubbed value instead, e.g. [null] or [{}].
The below is an answer to a previous version of the question. I kept it for reference, and because I think the content is useful for those who attempt to mock promise-returning functions in epic tests.
I think your problem is the from(axios.post(...)) in your epic. Axios returns a promise, and the RxJS TestScheduler has no way of making that synchronous, so expectObservable will not work as intended.
The way I usually address this is to create a simple wrapper module that does Promise-to-Observable conversion. In your case, it could look like this:
// api.js
import axios from 'axios';
import { map } from 'rxjs/operators';
export function post(path, data) {
return from(axios.post(path, options));
}
Once you have this wrapper, you can mock the function to return a constant Observable, taking promises completely out of the picture. If you do this with Jest, you can mock the module directly:
import * as api from '../api.js';
jest.mock('../api.js');
// In the test:
api.post.mockReturnValue(of(/* the response */));
Otherwise, you can also use redux-observable's dependency injection mechanism to inject the API module. Your epic would then receive it as third argument:
export const updateRemoteFieldEpic = (action$, state, { api }) =>
action$.pipe(
ofType(UPDATE_REMOTE_FIELD),
filter(({ payload: { update = true } }) => update),
mergeMap(({ payload }) => {
// ...
return api.post(...).pipe(...);
})
);
In your test, you would then just passed a mocked api object.

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

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