How do we mock fetch for Redux Async Actions? - javascript

In the Writing Tests section of Redux, how does the store.dispatch(actions.fetchTodos()) not invoke the fetch method, if store.dispatch is literally calling actions.fetchTodos?
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})

It is calling the fetch method, but the nock line:
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
stubs out the HTTP request so that fetch simply returns the right data (without actually hitting the endpoint).
Another option is to extract the call to fetch into another module — say, api — and use it inside your action creator. In tests, you would simply mock out the appropriate methods on the api module.

Related

Using FetchMock to mock fetch not working

Im trying to mock a fetch request to confirm if the right actions were dispatched:
the redux function:
export function loginRequest(email, password) {
return (dispatch) => {
dispatch(login(email, password))
return fetch('http://localhost:8000/login-success.json')
.then((response) => response.json())
.then((response) => dispatch(loginSuccess()))
.catch((error)=>dispatch(loginFailure()))
}
}
the test:
test("API returns the right response, the store received two actions LOGIN and LOGGING_SUCCESS", () => {
const store = mockStore({})
fetchMock.get('*', {})
return store.dispatch(loginRequest('jack', 124)).then(()=>{
const actions = store.getActions()
console.log(actions)
expect(actions).toEqual([login('jack', 124), loginSuccess()])
})
})
the console.log output:
[
{ type: 'LOGIN', user: { email: 'jack', password: 124 } },
{ type: 'LOGIN_FAILURE' }
]
im expecting the second action to be LOGIN_SUCCESS action instead. it seems like the mock isnt working at all. Am i missing something here
SOLVED: in the file where i defined the loginRequest function, i was importing fetch (import fetch from 'node-fetch';) hence calling the function in the test resulted in an actual fetch call rather than fetch-mock

Variable data is not persisted on client-side when using async fetch in Nuxt and data doesn't persist

I'm using the async fetch hook to render the component in SSR and making the API call inside the same. But on the live server, it is actually loading one more time on the client-side and making one more call to API and as API isn't exposed during client-side so data just washes away and the array object gets again set to empty.
data() {
return {
errored: false,
randomData: [],
};
},
async fetch() {
await this.$axios
.get(this.routeUrl)
.then((res) => {
if (res.data.length == 0 || res.status != 200) this.errored = true;
this.randomData = res.data;
})
.catch((err) => {
console.log(err);
});
},
fetchOnServer: true,
I want to persist this randomData variable, so there shouldn't be another call on the client-side to populate the same.
If you want to pass some data from the server-side into the client-side, you could use the nuxtServerInit Vuex action
/store/index.js
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}
So, the issue was that, as I was Docker and the API was only exposed to the server-side and not the client-side. For a blink, the content was available and then just all gone. Because the async fetch and asyncData hooks are called on the server and as well as on the client-side. I solved it using the Vuex store. Now I started storing the data fetched on the server in store and using the same on the client-side. Here is how I implemented that:
// store/index.js
export const state = () => ({
randomData: [],
});
export const mutations = {
SET_RANDOMDATA: (state, payload) => {
state.randomData = payload;
},
};
// Inside the component
// Fetch the data from API on server-side
async fetch() {
if (process.server) {
await this.$axios
.get(this.routeUrl)
.then((res) => {
this.$store.commit("SET_RANDOMDATA", res.data);
})
.catch((err) => {
console.log(err);
});
}
},
computed: {
...mapState(["randomData"]),
},
fetchOnServer: true,

How to properly test function calls inside Redux Toolkit's createAsyncThunks - Jest + Redux Toolkit

I am using Redux Toolkit's createAsyncThunk for an API request. This specific thunk is calling a sepparate non-anonymous function as its second parameter:
export const onInfoSubmit = createAsyncThunk('info/onInfoSubmit', handlePayload)
The handlePayload function goes like:
export async function handlePayload (data, { dispatch, rejectWithValue, getState }) {
dispatch(updateInfo({ isLoadingInfoRequest: true }))
if (!isFeatureEnabled(FEATURE_NAME)) {
return handleUpdateInfo(data, { dispatch, rejectWithValue, getState })
}
return thirdPartyApiCall()
.then((response) => {
data.thirdPartyResponse = response
return handleUpdateInfo(data, { dispatch, rejectWithValue, getState })
})
}
The handleUpdateInfo function is used to set Session Storage items, send a PUT request with the dynamically created payload with an axios call and, in case of failures or in case of response errors, trigger reject accordingly.
My question is, how could I test if, for instance, calling mockStore.dispatch(onInfoSubmit) calls the handlePayload function, and further on (if handlePayload calls handleUpdateInfo, for instance)?
I have tried the following approaches:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
jest.mock('./InfoSlice.js', () => ({
...jest.requireActual('./InfoSlice'),
handlePayload: jest.fn((data) => new Promise((resolve, reject) => resolve(data))),
handleUpdateInfo: jest.fn((data) => new Promise((resolve, reject) => resolve(data)))
}))
describe('InfoSlice unit tests', () => {
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(window, 'sessionStorage', {
value: {
getItem: jest.fn(() => null),
setItem: jest.fn(() => null),
removeItem: jest.fn(() => null)
},
writable: true
})
})
afterEach(() => {
jest.restoreAllMocks()
})
const initialState = {
loginInfo: {
userName: 'test#test.com',
email: 'test#test.com'
},
verifyEmailResponse: {},
email: {
loginInfo: {
email: 'test#test.com'
}
}
}
it('onInfoSubmit calls handlePayload', async () => {
const store = mockStore(initialState)
await store.dispatch(onEmailSubmit({
loginInfo: {
userName: 'new#test.com',
email: 'new#test.com',
}
}))
expect(handlePayload).toHaveBeenCalled()
})
But for some reason, handlePayload never gets called in this test.
Trying to check if handleUpdateInfo gets called in the same manner, also results in a broken test...
Besides, I cannot really seem to get any of the mock functions called on these tests.
Any tips on this issue will be much appreciated!
Technically, you are mocking the export with the name handlePayload - but inside that file, onInfoSubmit has still been created with the local function definition. You would probably have to split those over a file boundary to make that work.
Generally though, that test would not be testing anything besides the question if you have actually written handlePayload in that line - the test does not give you any confidence that your code is really working.
I'd recommend to mock as little as possible of your own code, but only mock things like apis (using msw for example) and window.localStorage. Then run your thunks in a real-life environment and see it your component works. We recommend doing real-life integration tests in the Redux docs as that will give you the most confidence on actual functionality, not just implementation details.

Why does Promises.all doesn't work in my vuex store

I'm facing a weird issue right now. I have a vuex store in my vue project which is seperated in different modules. I want to use Promise.all() to execute two independent async vuex action at once to enjoy the advantage of the first fail behavior.
store/modules/categories:
async CATEGORIES({ rootState }) {
const response = await axios.post('link_to_api', {
// some arguments for the api
arg: rootState.args
})
return response
}
store/modules/transportation:
async TRANSPORTATION({ rootState }) {
const response = await axios.post('link_to_api', {
// some arguments for the api
arg: rootState.args
})
return response
}
I know want to call those async functions in Promise.all:
store/modules/categories:
async PUT_CATEGORIES({ commit, dispatch, rootState }) {
try {
const [resCategories, resTransportation] = await Promise.all([
dispatch('CATEGORIES').catch(err => { console.log('Fehler bei Kabinenabfrage!'); throw {error: err, origin: 'kabine'}; }),
dispatch('transportation/TRANSPORTATION', {root:true}).catch(err => { console.log('Fehler bei Flugabfrage!'); throw {error: err, origin: 'flug'}; })
])
//do something after both promises resolved
} catch(error) {
// do something if one promise rejected
commit('errorlog/ERROR', 4, {root:true})
dispatch("errorlog/LOG_ERROR", {'origin': '2', 'error_code': '113', 'message': error.toString()}, {root:true})
router.push({path: '/Error'})
}
I get the following error:
This is weird because I used {root:true} and the prefix transport in dispatch to access the action of the transport module in store. This works great for the LOG_ERROR action in the errorlog module I use in the catch block.
If I copy the TRANSPORTATION action in the categories module it works great...
Has anybody faced this issue before and has an advice??
Thanks in advance!
In your case, {root:true} is passed as second argument although it should be passed as third.
- dispatch('transportation/TRANSPORTATION', {root:true})
+ dispatch('transportation/TRANSPORTATION', null, {root:true})
According to vuex's doc
To dispatch actions or commit mutations in the global namespace, pass { root: true } as the 3rd argument to dispatch and commit.
They also provide a sample code (which is further simplified here)
modules: {
foo: {
namespaced: true,
actions: {
// dispatch and commit are also localized for this module
// they will accept `root` option for the root dispatch/commit
someAction ({ dispatch, commit, getters, rootGetters }) {
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

State Vuex don't work correctly with Nuxt.js Problem

I'm trying to set state vuex on my Nuxt.js App, but it don't work correctly.
So here i'm set my state with fetch method:
fetch({app, store, route}) {
app.$axios.$get(`apps/${route.params.id}`)
.then(res => {
store.dispatch('setApp', {
...res,
id: route.params.id
})
console.log(app.store.state.app);
})
}
Here everything works correcty when i'm logging app.store, data is there,
but when i'm trying to log it in:
created() {
console.log(this.$store);
},
mounted() {
console.log(this.$store);
}
Here's my store code:
const store = () => new Vuex.Store({
state: {
app: ''
},
mutations: {
'SET_APP' (state, payload) {
state.app = payload
}
},
actions: {
setApp (ctx, payload) {
ctx.commit('SET_APP', payload)
}
},
getters: {
}
})
i't don't work my state is empty so data is not render on my template (
I hope that somebody gotta help me !
P.S: Also when it's logging on client-side everything works fine, but in SSR not (
As stated from the docs
To make the fetch method asynchronous, return a Promise, Nuxt.js will wait for the promise to be resolved before rendering the component.
In code example above, axios request is made inside fetch hook, but Nuxt will not wait for it to resolve, as no promise is returned, so it keeps rendering the component, thus running created and mounted hooks, which by the time of invocation do not have filled $store.
In order to make it work, you should return a promise from fetch method.
If no additional code is present in your app, simply adding return statement will solve the issue:
fetch({app, store, route}) {
return app.$axios.$get(`apps/${route.params.id}`)
.then(res => {
store.dispatch('setApp', {
...res,
id: route.params.id
})
console.log(app.store.state.app);
})
}

Categories