How to test Vuex module using Mocha and Chai - vue.js

I'm currently using Nuxt and I would like to do unit testing in my vuex modules.
I'm using vuex modules and here's my index.js
store/index.js
import Vuex from 'vuex';
import * as user from "~/store/modules/users";
const createStore = () => {
return new Vuex.Store({
state: {},
mutations: {},
modules: {
user
}
});
};
export default createStore;
and here's the vuex for users (I made it very simple by mutating the count only but I'm doing api requests here)
store/modules/user.js
export const state = {
count: 0
}
export const mutations = {
INCREMENT(state, user) {
state.count += 1;
}
}
export const actions = {
incrementCount({commit}) {
commit('INCREMENT');
}
}
export const getters = {
count(state) {
return state.count;
}
}
I would like to use Mocha and Chai, for testing state, actions, mutations, and getters for each vuex modules.
Can anyone give a sample for doing a unit test for this?

Related

Vue3 testing composition API with vuex in vitest

I'm having trouble getting a mock action to run using Vue3 while testing with vitest.
I have a component which calls out to a modularized vuex store that is imported into my component using the composition api. Something like the following.
export default defineComponent({
setup() {
const { doAction } = useModActions([
'doAction'
])
}
})
I use createNamespacedHelpers to setup my store module from the vuex-composition-helpers library.
After I use useStore with a Symbol key to setup the state of my store. I consume it in my application by doing
app.use(store, key)
To mock it in my tests I was trying the following
const actions = {
doAction: vi.fn()
}
const spy = vi.spyOn(actions, 'doAction')
const mockStore = createStore({
modules: {
mod: {
namespaced: true,
actions
}
}
})
const wrapper = mount(Component, {
global: {
provide: { [key]: mockStore }
}
})
But my spy is never called and my component always calls the original implementation. Is there a way to get all these pieces working together?
The mockStore here (from Vuex's createStore()) is an instance of a Vue plugin, which should be passed to the global.plugins mounting option (not global.provide):
// MyComponent.spec.js
import { describe, it, expect, vi } from 'vitest'
import { mount } from '#vue/test-utils'
import { createStore } from 'vuex'
import MyComponent from '../MyComponent.vue'
describe('MyComponent', () => {
it('button calls doAction', async () => {
const actions = {
doAction: vi.fn(),
}
const mockStore = createStore({
modules: {
myModule: {
namespaced: true,
actions,
},
},
})
const wrapper = mount(MyComponent, {
global: {
plugins: [mockStore], // 👈
},
})
await wrapper.find("button").trigger("click")
expect(actions.doAction).toHaveBeenCalled()
})
})
demo

Modules vs Multiple vuex store files

I am working on a project using vue and vuex. And I think most of the people are having the problem, after sometime the store.js (or index.js) is getting too big. So I want to split the store.js file. After some google I found I can use Modules to overcome this problem. BUT I tried also with creating a new Instance of vuex and it works perfectly fine.
Single instance with modules :
---store.js
import Vuex from "vuex";
import thisismodule1 from "./modules/module1";
import thisismodule2 from "./modules/module2";
const createStore = () => {
return new Vuex.Store({
modules: {
module1: thisismodule1,
module2: thisismodule2
}
});
};
export default createStore;
const store = new Vuex.Store({
module
});
Multiple files with multiple instances:
---storeCar.js
---storeHouse.js
---storeTree.js
...
So my question is, is this allowed or do I have to use modules with single instance?
Thank you in advance!
there is a best practice for that:
Create a file. that's name is Shared
Create a Store folder and create a modules folder on it:
you should modules in the modules folder and define your store for a target:
for example:
import * as types from "../types";
const state = {
currentPage: {}
};
const getters = {
[types.avatarManagement.getters.AVATAR_MANAGEMENT_GET]: state => {
return state.currentPage;
}
};
const mutations = {
[types.avatarManagement.mutations.AVATAR_MANAGEMENT_MUTATE]: (
state,
payload
) => {
state.currentPage = payload;
}
};
const actions = {
[types.avatarManagement.actions.AVATAR_MANAGEMENT_ACTION]: (
{ commit },
payload
) => {
commit(types.avatarManagement.mutations.AVATAR_MANAGEMENT_MUTATE, payload);
}
};
export default {
state,
getters,
mutations,
actions
};
Create index.js for define Vuex and import your modules:
index.js file:
import Vue from "vue";
import Vuex from "vuex";
import avatarManagement from "./modules/avatarManagement";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
avatarManagement
}
});
5) also you can types of your vuex store on Type.js file:
type.js:
export const avatarManagement = {
getters: {
AVATAR_MANAGEMENT_GET: "AVATAR_MANAGEMENT_GET"
},
mutations: {
AVATAR_MANAGEMENT_MUTATE: "AVATAR_MANAGEMENT_MUTATE"
},
actions: {
AVATAR_MANAGEMENT_ACTION: "AVATAR_MANAGEMENT_ACTION"
}
};
***for get data from Store:
computed: {
...mapGetters({
registrationData:types.avatarManagement.AVATAR_MANAGEMENT_GET,
getDataFromStore() {
return this.registrationData;
}
}
***for Change data to Store and mutate that:
methods: {
goToActivity() {
const activity = {
companyList: this.categories
};
this.$store.commit(types.avatarManagement.AVATAR_MANAGEMENT_MUTATE, {
newData
});
},
}

Access app.config.globalProperties in vuex store

I got a vuex store like this:
const state = {
status: '',
};
const getters = {
//...
};
const actions = {
// ...
};
const mutations = {
// ...
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
Now I'd like to access app.config.globalProperties.$notify
In Vue.js2 I was using something like Vue.prototype.$notify, but this is not working anymore.
$notify is also provided like this:
app.use(routes)
.provide('notify', app.config.globalProperties.$notify)
.mount('#app')
Unfortunately I did not find any information about this in the docs yet.
So my question: How can I either inject $notify or access app.config.globalProperties within this store?
From your store and its modules, you could return a store factory -- a function that receives the application instance from createApp and returns a store:
// store/modules/my-module.js
const createStore = app => {
const mutations = {
test (state, { committedItem }) {
app.config.globalProperties.$notify('commited item: ' + committedItem)
}
}
return {
namespaced: true,
//...
mutations,
}
}
export default app => createStore(app)
// store/index.js
import { createStore } from 'vuex'
import myModule from './modules/my-module'
export default app =>
createStore({
modules: {
myModule: myModule(app)
}
})
Then use the store factory like this:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import createStore from './store'
const app = createApp(App)
app.use(createStore(app)).mount('#app')
demo

How to access router from nuxtjs store?

I tried this but only got an error message
This relative module was not found:
* ./router in ./store/index.js
// Ran with yarn dev
import router from './router'
export const state = () => ({
authenticated: false,
})
export const mutations = {
toggleLogin(state, loginStatus, path) {
state.authenticated = loginStatus
if (loginStatus) {
router.push(path)
} else {
router.push('login')
}
},
}
My store path is store/index.js
Vuex mutations and getters doesn't have access to the Vue context. Only actions does.
Also, you can't import the Nuxt router like that. What's that file you're trying to import?
I'd suggest to make an action instead:
export const actions: {
toggleLogin({ commit }, payload) {
this.app.router.push('') //exists
}
}

vuex unknown action type when attempting to dispatch action from vuejs component

I'm using laravel, vue and vuex in another project with almost identical code and it's working great. I'm trying to adapt what I've done there to this project, using that code as boilerplate but I keep getting the error:
[vuex] unknown action type: panels/GET_PANEL
I have an index.js in the store directory which then imports namespaced store modules, to keep things tidy:
import Vue from "vue";
import Vuex from "vuex";
var axios = require("axios");
import users from "./users";
import subscriptions from "./subscriptions";
import blocks from "./blocks";
import panels from "./panels";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
actions: {
},
mutations: {
},
modules: {
users,
subscriptions,
blocks,
panels
}
})
panels.js:
const state = {
panel: []
}
const getters = {
}
const actions = {
GET_PANEL : async ({ state, commit }, panel_id) => {
let { data } = await axios.get('/api/panel/'+panel_id)
commit('SET_PANEL', data)
}
}
const mutations = {
SET_PANEL (state, panel) {
state.panel = panel
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Below is the script section from my vue component:
<script>
import { mapState, mapActions } from "vuex";
export default {
data () {
return {
}
},
mounted() {
this.$store.dispatch('panels/GET_PANEL', 6)
},
computed:
mapState({
panel: state => state.panels.panel
}),
methods: {
...mapActions([
"panels/GET_PANEL"
])
}
}
</script>
And here is the relevant code from my app.js:
import Vue from 'vue';
import Vuex from 'vuex'
import store from './store';
Vue.use(Vuex)
const app = new Vue({
store: store,
}).$mount('#bsrwrap')
UPDATE:: I've tried to just log the initial state from vuex and I get: Error in mounted hook: "ReferenceError: panel is not defined. I tried creating another, very basic components using another module store, no luck there either. I checked my vuex version, 3.1.0, the latest. Seems to be something in the app.js or store, since the problem persists across multiple modules.
Once you have namespaced module use the following mapping:
...mapActions("panels", ["GET_PANEL"])
Where first argument is module's namespace and second is array of actions to map.