Vue Pinia function is undefined in onMounted when unit test is ran - javascript

I have a component and a Pinia store which contains a state and some actions. The code works perfectly fine in browser and in E2E (cypress) tests, but fail on unit tests. I'm using vue-testing-utils and vitest.
The store function can be called fine from the unit test when the button is clicked, but if the function is in the mounted or main script, it fails the test
src/components/UsersComponent.vue
<script setup>
import { onMounted } from 'vue'
import { useUsersStore } from '#/stores/users.store'
const usersStore = useUsersStore()
// usersStore.resetStatus() // <- This fails in the unit test
onMounted(() => {
usersStore.resetStatus() // <- This fails in the unit test
})
function changeStatus() {
usersStore.changeStatus() // <- This passes in the unit test
}
</script>
<template>
<div>
<p>Status: {{ usersStore.status }}</p>
<button #click="changeStatus()">Change Status</button>
</div>
</template>
src/stores/users.store.js
import { defineStore } from 'pinia'
import { usersAPI } from '#/gateways'
export const useUsersStore = defineStore({
id: 'users',
persist: true,
state: () => ({
status: 'ready',
}),
getters: {},
actions: {
resetStatus() {
this.status = 'ready'
},
changeStatus() {
this.status = 'loading'
},
},
})
src/components/tests/UsersComponent.spec.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '#vue/test-utils'
import { createTestingPinia } from '#pinia/testing'
import UsersComponent from '#/components/UsersComponent.vue'
import { useUsersStore } from '#/stores/users.store'
const wrapper = mount(UsersComponent, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn() })],
},
})
const usersStore = useUsersStore()
describe('UsersComponent', () => {
it('store function is called', async () => {
// arrange
const spy = vi.spyOn(usersStore, 'resetStatus')
const button = wrapper.find('button')
// act
await button.trigger('click')
// assert
expect(spy).toHaveBeenCalled()
})
})
The unit tests return 2 different error. The first is a console log when the function tries to run in onMounted() and the second is what vitest returns.
stderr | unknown test
[Vue warn]: Unhandled error during execution of mounted hook
at <UsersComponent ref="VTU_COMPONENT" >
at <VTUROOT>
FAIL src/components/__tests__/UsersComponent.spec.js [ src/components/__tests__/UsersComponent.spec.js ]
TypeError: usersStore.resetStatus is not a function
❯ src/components/UsersComponent.vue:16:14
16|
17| <template>
18| <div>
| ^
19| <p>Status: {{ usersStore.status }}</p>
20| <button #click="changeStatus()">Change Status</button>
I know this example is a little basic and doesn't really serve a purpose, but I'm wondering how I can have store functions inside the onMounted() (or similar places) without it breaking all my unit tests.

Maybe this can be useful to you:
describe('UsersComponent', () => {
it('changeStatus function is called', async () => {
const wrapper = mount(UsersComponent, {
mounted: vi.fn(), // With this you mock the onMounted
global: {
plugins: [createTestingPinia({
initialState: { // Initialize the state
users: { status: 'ready' },
}
})]
}
})
// Spy the method you call...
const spy = vi.spyOn(wrapper.vm, 'changeStatus');
wrapper.vm.changeStatus()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})

Related

Cannot create component with shallowMount, vm.$refs['VTU_COMPONENT'] is not defined

Not sure if it is a bug or If I'm doing something wrong.
I try to mount my main App component with shallowMount but it doesn't works. I get the following error message:
Cannot set properties of undefined (setting 'hasOwnProperty')
It happens when vue-test-utils try to mount the App Component:
...
var appRef = vm.$refs[MOUNT_COMPONENT_REF];
// we add `hasOwnProperty` so jest can spy on the proxied vm without throwing
appRef.hasOwnProperty = function (property) {
return Reflect.has(appRef, property);
};
console.warn = warnSave;
var wrapper = createVueWrapper(app, appRef, setProps);
trackInstance(wrapper);
return wrapper;
...
Here, MOUNT_COMPONENT_REF equals 'VTU_COMPONENT' and vm.$refs[MOUNT_COMPONENT_REF] isn't defined.
Minimal example
It is available online : https://codesandbox.io/s/prod-glitter-tnoyqt?file=/src/App.spec.js
App.vue
<template>
<div id="a-test">
{{ test }}
</div>
</template>
<script>
export default {
name: "App",
};
</script>
App.spec.js
import { shallowMount } from "#vue/test-utils";
import App from "#/App";
describe("App.vue", () => {
test("Test that fails", async () => {
const wrapper = shallowMount(App);
});
});
fixed if I upgrade to #vue/test-utils 2.0.0-rc.21

jest mock vuex useStore() with vue 3 composition api

I'm trying to unit test a component where you click a button which should then call store.dispatch('favoritesState/deleteFavorite').
This action then calls an api and does it's thing. I don't want to test the implementation of the vuex store, just that the vuex action is called when you click the button in the component.
The Component looks like this
<template>
<ion-item :id="favorite.key">
<ion-thumbnail class="clickable-item remove-favorite-item" #click="removeFavorite()" slot="end" id="favorite-star-thumbnail">
</ion-thumbnail>
</ion-item>
</template>
import {useStore} from "#/store";
export default defineComponent({
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", props.item.id);
}
return {
removeFavorite,
}
}
});
The jest test
import {store} from "#/store";
test(`${index}) Test remove favorite for : ${mockItemObj.kind}`, async () => {
const wrapper = mount(FavoriteItem, {
propsData: {
favorite: mockItemObj
},
global: {
plugins: [store]
}
});
const spyDispatch = jest.spyOn(store, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
});
I have tried different solutions with the same outcome. Whenever the "trigger('click')" is run it throws this error:
Cannot read properties of undefined (reading 'dispatch') TypeError:
Cannot read properties of undefined (reading 'dispatch')
The project is written in vue3 with typescript using composition API and vuex4
I found a solution to my problem.
This is the solution I ended up with.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}

Testing effects NgRx Angular 11

I am having problems while trying to test effects in my Angular 11 project. I use the karma runner. It would seem that when I dispatch an action in my tests, the effects does't seem to respond to it.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let actions$: any;
let effects: SimpleEffects;
let store: MockStore<Store>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
effects = TestBed.inject(SimpleEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
actions$ = of(SimpleActionTypes.UnavailablePrice);
// Subscribe to the effect
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
I have tried many ways to enter the subscribe part of my effect (using marbles hot cold ...), but it doesn't seem to work. I have a failure indicating :
"SimpleEffects should fire with a default price FAILED
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)"
The micro project is hosted here : https://github.com/Ushelajyan/simple-ngrx-testing-effects
Thank you in advance for your help.
You override actions$ within your test (it-block). Unfortunately the TestBed.configureTestingModule() gets executed inside a beforeEach block, which happens right before the it-block gets executed.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let store: MockStore<Store>;
const createEffects = (actions$) => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
return TestBed.inject(SimpleEffects);
};
it('should be created', () => {
const effects = createEffects(of(undefined));
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
const actions$ = of(SimpleActionTypes.UnavailablePrice);
// Create the effect with your given mock
const effects = createEffects(actions$)
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
This should do the trick.
I added a createEffect function that configures your TestBed properly with the given actions$ mock and returns a new instance of SimpleEffects.
This instance can then be used to subscribe to its defined effects.

How to stub a module function with Cypress?

I want to create a test with Cypress that has a React component that uses an auth library (#okta/okta-react) with a HOC (withOktaAuth).
My component looks like this:
// Welcome.js
import { withOktaAuth } from '#okta/okta-react'
const Welcome = ({authState}) => {
return <div>{authState.isAuthenticated ? 'stubbed' : 'not working'}</div>
}
export default withOktaAuth(Welcome)
I tried to make a test like so:
// test.js
import * as OktaReact from '#okta/okta-react'
const withOktaAuthStub = Component => {
Component.defaultProps = {
...Component.defaultProps,
authState: {
isAuthenticated: true,
isPending: false
},
authService: {
accessToken: '123'
}
}
return Component
}
describe('Test auth', () => {
before(() => {
cy.stub(OktaReact, 'withOktaAuth').callsFake(withOktaAuthStub)
})
it('Stubs auth', () => {
cy.visit('/welcome')
cy.contains('stubbed')
})
})
When I run the test, the component still does not use the stubbed function. Any help is very much appreciated!
It's been 2 years that this questions was submitted, but for those who still encounters that error, Cypress provides a guide about Okta e2e testing: https://docs.cypress.io/guides/end-to-end-testing/okta-authentication#Adapting-the-back-end

How to programmatically set $route.params when testing a Vuejs component

I have a Vue.js component that has different behavior according to the $route.params or $route.query. Something like:
<template>
<div class="hello">
{{query}}
</div>
</template>
<script>
export default {
name: 'hello',
data () {
return {
}
}
},
computed: {
'query': () => {
return $route.params.query
}
}
}
</script>
How can I write a unit test spec where I can programmatically set the value of $route.params or $route.query ?
Something like this:
import Vue from 'vue'
import Template from '#/components/Template'
describe('Template.vue', () => {
it('should render according to query', () => {
const Constructor = Vue.extend(Script)
const vm = new Constructor()
vm.$route = { // THIS DOES NOT WORK
params: {
id: 1
}
}
vm.$mount()
// Test contents
})
})
Fails with
setting a property that has only a getter

Categories

Resources