Vue 2 Testing With Pinia
So I do some work for a company where we are building an application in vue. When we started vue3 had literally only just been released, the ecosystem around it was very immature and a couple of key components (vuetify) hadn't even been ported over yet, so at the time we decided to write the app using vue2.
However, fast forward a year, and the vue3 ecosystem is a lot more stable and development has stopped on vue2, so it looks like its time to make the upgrade. Now the application isn't that huge, but moving all the components over will take time, luckily we should be able to do it gradually instead of big bang as vue2 components can be written using the new composition API.
Another change is the move away from Vuex to Pinia as the state store. Again this is backward compatible with vue2, so this seemed like a good place to start.
I'm not going to talk much about the actual migration of the stores here, but it was actually pretty straightforward forward and they are much more pleasnt to use than vuex stores, the issue I had was testing with them.
Please just give me an example! 📝
My issue came when I tried to move my tests over from once the Vuex stores had been replaced in the components with Pinia ones. Obviously, they all started to fail, so I headed over to the testing section of the documentation (https://pinia.vuejs.org/cookbook/testing.html) to see how to get going.
Now it might just be me but I struggled to understand a couple of the things going on here. My main issue was there's a lack of example component test online showing a simply create and inject a mocked pinia store into the component you are testing. After much head-scratching I finally got one working, as below:
import { mount, createLocalVue } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent'
import { PiniaVuePlugin, setActivePinia, defineStore } from 'pinia'
import { createTestingPinia } from '@pinia/testing'
const localVue = createLocalVue()
localVue.use(PiniaVuePlugin)
describe('MyComponent.vue', () => {
let wrapper, ourPinia
//create pinia instance. No autostub we control the outcomes
ourPinia = createTestingPinia({
stubActions: false,
})
//activate our instance
setActivePinia(ourPinia)
const actionId = 1
const actionsResponse = [
{
id: actionId,
triggerFormSubmit: true
}
]
})
const mountWithProps = function () {
const app = document.createElement('div')
app.setAttribute('data-app', true)
document.body.append(app)
wrapper = mount(MyComponent, {
localVue,
pinia: ourPinia,
})
}
beforeEach(() => {
//setup our store
const useMyStore = defineStore('MyStore', {
state: () => ({ actions: actionsResponse }),
getters: {
getAllActions: (state) => state.actions,
},
actions: {
setActions() {
return new Promise(() => {
this.actions = actionsResponse
})
},
},
})
useMyStore(ourPinia)
})
afterEach(() => {
wrapper.destroy()
})
it('onActionTrigger with triggerFormSubmit set emits event', () => {
// assign
mountWithProps()
// act
wrapper.vm.onActionTrigger(actionId)
// assert
wrapper.vm.$nextTick(() => {
expect(wrapper.emitted().actionWorkFlowSubmitTrigger).toBeTruthy()
})
})
})
So, what's going on? 🤔
The first couple of bits are actually pretty well documented. We create a localvue and push the PiniaVuePlugin in. Then we create a testing pinia instance using createTestingPinia, here I'm also telling it not to auto-mock the actions so we can decide what happens. Then we activate our instance ready to use.
Once we have our instance setup we need to create our mocked store. Make sure to name it the same as the 'real' one so it drops in over the top of it. We define our store as if creating a real one, but here we just mock out the state/getters/actions. Then we pass our store into the active Pinia instance.
One of the bits that had me stumped for a while was by default if you don't include the stubActions: false directive when creating the test Pinia then all the actions get auto-mocked. These could be fine in some instances, but the actions don't seem to return promises so all my calls failed as I waited for them to resolve. This is why I am manually mocking any actions and making sure to return a promise where required.