Redux Async Test with Webpack+karma+etc - testing

I was trying to do a async action test but failed miserable :(
the test in question is this one: https://github.com/persocon/destiny-weekly/blob/test/test/actions/index.spec.jsx
I'm getting this error message:
1) fill in GET_OPTIONS when fetching all options is done
Async Actions
undefined is not an object (evaluating 'store.dispatch(actions.getOptions()).then')
/Users/persocon/Projects/destiny-weekly/test/test.bundle.js:14669:42 <- webpack:///test/actions/index.spec.jsx:49:7
and I have no clue what that means, if this was to help I'm more confuse then ever.
UPDATE
had to implement fetch on the action itself instead of the $.get I was using but now I get a new error pointing to my action :v even it working on the browser:
1) fill in GET_OPTIONS when fetching all options is done
Async Actions
Can't find variable: fetch
/Users/persocon/Projects/destiny-weekly/test/test.bundle.js:42473:9 <- webpack:///src/app/javascript/actions/index.jsx:32:9
/Users/persocon/Projects/destiny-weekly/test/test.bundle.js:15691:23 <- webpack:///~/redux-thunk/lib/index.js:12:0
/Users/persocon/Projects/destiny-weekly/test/test.bundle.js:14669:20 <- webpack:///test/actions/index.spec.jsx:48:19
UPDATE 2
Action code:
const setOptions = (result) => {
return {
type: 'GET_OPTIONS',
options: result
}
}
const getOptions = () => {
return dispatch => {
dispatch(startLoading())
return fetch('/api/selectActivity')
.then(response => response.json())
.then( json => {
dispatch(doneLoading());
json.unshift({advisorTypeCategory: "Selecione Uma Atividade", identifier: "", disabled: "disabled"});
dispatch(setOptions(json));
}
)
}
}

Ya, the Async Action Creators Example is using 'fetch' which is a bit contrived, but should work in theory. One simple solution might be to implement it with this isomorphic fetch library. Good luck!

The solution was to implement the Isomorphic Fetch
Because as 4m1r told it was a contrived function and couldn't be found.

Related

Same situation but different results. I think that Try syntax might be the cause [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 months ago.
I encountered axios error in the process of creating my application.
The below code work fine.
in the first image,console.log output res.data.
let categoryId = ''
axios.get('/api/max')
.then((res) => {
console.log(res.data) // image part
categoryId = res.data.id
})
.catch((err) => {
console.log('ssssssuuuuu')
})
('api/max') return category with max ID.
The below code don't work well.
console.log(res) output properly, but console.log(res.data) output undefined.
try {
const res = axios.get('/api/max')
console.log(res)
console.log(res.data) // undefined
categoryId = res.data.id
console.log('a')
} catch (err) {
console.log(err.message)
}
what causes undefined?
I googled but, I didn't know the cause.
I'm sorry that my English is not very good.
Thank you for your help.
You should use await axios.get('/api/max') before using console.log since it's an async call. (you can also use .then but it's less friendly)
Of course, don't forget to wrap your function into an async when using await.
Also, keep in mind that quirk when using console.log. Prefer using Vue devtools to inspect the current state you do have on your page.
Overall, I recommend the usage of async/await over .then all the time.
Here is a nice documentation for that one: https://javascript.info/async-await

Ngrx waiting for Multiple API calls in effect

I'm super new about ngrx and I'm trying to develop an effect to dispatch multiple calls to my API to retrive a child list of objects.
Here my code.
loadMyChildren$ = createEffect (() => this.actions$.pipe(
ofType(SomeActions.loadMyChildren),
switchMap(({parentsObjArr}) => {
const obsList$: Observable<ChildModel>[] = parentsObjArr.result.map(parentsObj => this.childrenService.loadTimeSeries(parentsObj));
let childrenArr: ChildModel[] = [];
const source$ = zip(obsList$);
source$.subscribe((res) =>{
childrenArr = res;
}, err => { console.log(err); });
console.log('childrenArr', childrenArr)
return [
SomeActions.loadChildrenSuccess({childrenArr}),
SomeActionsTwo.loadParentsSuccess({parentsObjArr})
]
}),
catchError((err) => {
return of(SomeActions.loadMyChildrenFailed)
})
)
Unfortunately my effect seem doesn't wait for my API requests complete on "zip" (I tried also combineLatest and forkJoin) before dispatching the actions on the return array... "console.log" of my objects array is always empty and the store, consequently, does not keep any data.
What I'm wrong?
rxjs 6.6.0
angular 13.2.3
It's not good practise to manualy subscribe inside switchMap.
You are right, your code returns action before your API requests completes, you need to manipultate the stream without subscribing inside.
I would refactor your coude, so source$ is returned in switchMap, but its result is maped to desired ngrx actions with props that you wanted.
It would looked something like that:
switchMap(({parentsObjArr}) =>
zip(parentsObjArr.result.map(parentsObj => this.childrenService.loadTimeSeries(parentsObj)))
.pipe(
map(child => [
SomeActions.loadChildrenSuccess({child}),
SomeActionsTwo.loadParentsSuccess({parentsObjArr})
]
)
)
)

How to array destructure a Promise.all in Nuxt's asyncData

I am working with Nuxt and Vue, with MySQL database, all of which are new to me. I am transitioning out of WebMatrix, where I had a single Admin page for multiple tables, with dropdowns for selecting a particular option. On this page, I could elect to add, edit or delete the selected option, say a composer or music piece. Here is some code for just 2 of the tables (gets a runtime error of module build failed):
<script>
export default {
async asyncData(context) {
let [{arrangers}, {composers}] = await Promise.all([
context.$axios.get(`/api/arrangers`),
context.$axios.get(`/api/composers`),
])
const {arrangers} = await context.$axios.get('/api/arrangers')
const {composers} = await context.$axios.get('/api/composers')
return { arrangers, composers }
},
}
</script>
You do have the same variable name for both the input (left part of Promise.all) and as the result from your axios call, to avoid naming collision, you can rename the result and return this:
const { arrangers: fetchedArrangers } = await context.$axios.get('/api/arrangers')
const { composers: fetchedComposers } = await context.$axios.get('/api/composers')
return { fetchedArrangers, fetchedComposers }
EDIT, this is how I'd write it
async asyncData({ $axios }) {
const [posts, comments] = await Promise.all([
$axios.$get('https://jsonplaceholder.typicode.com/posts'),
$axios.$get('https://jsonplaceholder.typicode.com/comments'),
])
console.log('posts', posts)
console.log('comments', comments)
return { posts, comments }
},
When you destructure at the end of the result of a Promise.all, you need to destructure depending of the result that you'll get from the API. Usually, you do have data, so { arrangers } or { composers } will usually not work. Of course, it depends of your own API and if you return this type of data.
Since destructuring 2 data is not doable, it's better to simply use array destructuring. This way, it will return the object with a data array inside of it.
To directly have access to the data, you can use the $get shortcut, which comes handy in our case. Directly destructuring $axios is a nice to have too, will remove the dispensable context.
In my example, I've used JSONplaceholder to have a classic API behavior (especially the data part) but it can work like this with any API.
Here is the end result.
Also, this is what happens if you simply use this.$axios.get: you will have the famous data that you will need to access to later on (.data) at some point to only use the useful part of the API's response. That's why I do love the $get shortcut, goes to the point faster.
PS: all of this is possible because Promise.all preserve the order of the calls: https://stackoverflow.com/a/28066851/8816585
EDIT2: an example on how to make it more flexible could be
async asyncData({ $axios }) {
const urlEndpointsToFetchFrom = ['comments', 'photos', 'albums', 'todos', 'posts']
const allResponses = await Promise.all(
urlEndpointsToFetchFrom.map((url) => $axios.$get(`https://jsonplaceholder.typicode.com/${url}`)),
)
const [comments, photos, albums, todos, posts] = allResponses
return { comments, photos, albums, todos, posts }
},
Of course, preserving the order in the array destructuring is important. It's maybe doable in a dynamic way but I don't know how tbh.
Also, I cannot recommend enough to also try the fetch() hook alternative someday. I found it more flexible and it does have a nice $fetchState.pending helper, more here: https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/ and in the article on the bottom of the page.

Understanding then() in Cypress

I am reading through the documentation in Cypress and I think I have an idea as to what then() does. It works like promises, where a promise returns another promise, but with then(), we are returning a new subject.
If we look at the code example below, we are using then() because we are returning a new variable, which in this case is called target.
Am I understanding this correctly? If not, can someone correct me?
it.only('Marks an incomplete item complete', () => {
//we'll need a route to stub the api call that updates our item
cy.fixture('todos')
.then(todos => {
//target is a single todo, taken from the head of the array. We can use this to define our route
const target = Cypress._.head(todos)
cy.route(
"PUT",
`api/todos/${target.id}`,
//Here we are mergin original item with an object literal
Cypress._.merge(target, {isComplete: true})
)
})
.then is used to receive the results from cy.fixture('todos'). The variable target is not significant in this code.
In your code sample, the variable that is returned from cy.fixture is named todos - the spacing of the code may be throwing you off here? The .then call is attached to the cy.fixture() call
// These 2 code blocks are the same - just different spacing
cy.fixture('todos')
.then(todos => {});
cy.fixture('todos').then(todos => {});
https://docs.cypress.io/api/commands/fixture.html#Usage
cy.fixture('logo.png').then((logo) => {
// load data from logo.png
})
Using .then() allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.
To put it simply, it is used to play around with the yield of the previous command and work around with it in that case. THEN() command is handy and helpful in debugging the yield of the previous command.
const baseURL = "https://jsonplaceholder.typicode.com";
describe("Get Call-Expect+ normal req", () => {
it("GetPostById-Expect", () => {
cy.request(baseURL + "/posts/1").as("GetPostById");
cy.get("#GetPostById").then((response) => {
//response: status
expect(response.status).to.equal(200);
expect(response.status).to.eq(200);
});
});
Refer: https://docs.cypress.io/api/commands/then#Promises

Vuex Object Shows Null in Vue Component

In the app I'm creating I have a base store for objects that might be used across the entire app, such as the logged in user, validation errors and the like.
I also have other namespaced modules for specific sections of my app.
When my parent component is loaded there is an ajax call that pulls in data and commits it to the various stores.
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => {
dispatch('base/setLoggedInUser', response.data.user, { root: true })
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
Then I use mapped getters to access items from my stores.
computed: {
...mapGetters({
user: 'base/getUser',
fetishList: 'setup/getFetishesList',
localeData: 'setup/getLocale',
colorsList: 'setup/getColorsList',
rolesList: 'setup/getRolesList',
genderList: 'setup/getGendersList',
orientationList: 'setup/getOrientationsList',
lookingList: 'setup/getLookingsList',
seekingList: 'setup/getSeekingsList',
validation: 'base/getValidationErrors',
}),
}
All is working as expected except for my user object.
In my Vue inspector I can see that the user object is stored properly in Vuex as expected, but when I console.log(this.user) I get null and anytime I try to access a user property I get console errors.
Can anyone explain why this might be happening, I've never seen this before and have no idea what I'm looking for?
Thanks.
My guess is that your dispatch() (Vue.js actions are ALWAYS expected to be async) is not completing properly. This is how I would rewrite it with a single caveat:
Your base/setLoggedInUser Vuex action MUST return a Promise for this to work properly.
/*
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => {
dispatch('base/setLoggedInUser', response.data.user, { root: true })
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
*/
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => Promise.all([
dispatch('base/setLoggedInUser', response.data.user, { root: true }),
Promise.resolve(response)
]))
.then(([dispatchResponse, response]) => {
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
There are two main posibilities here:
The first one is that you might not be defining properly the user getter.
The second one, console.log is being executed previous to the data being set by this action:
dispatch('base/setLoggedInUser', response.data.user, { root: true })
Vuex actions are asynchronous, so setLoggedInUser could have started before the console.log (and the code giving you errors) is executed, but the actual data might not have been received yet at that point (it would be undefined).
If this is the case, add the following condition to the part of the template or the component(s) that are using the block of code where you are getting those errors:
v-if="user"
This will make Vue to wait for the mapped getter user to have a value to mount said template segment or components, avoiding trying to access properties of undefined.