I have a component that when created() sets date: with null or with props.datainicial.
export default {
props: ['numeroDaParcela', 'datainicial'],
data () {
return {
date: null,
dateBR: '',
modal: false
}
},
created () {
if (this.datainicial === '' ||
this.datainicial === undefined) {
this.date = null
} else {
this.date = this.datainicial
}
}
In DevTools with no props:
In DevTools with some props:
When I do my test:
import { mount } from 'vue-test-utils'
import SelecionadorData from '#/components/Shared/SelecionadorData.vue'
describe('SelecionadorData.vue', () => {
it('should receive the props datainicial', () => {
const wrapper = mount(SelecionadorData)
wrapper.setProps({
datainicial: '2018-01-01'
})
expect(wrapper.vm.date).toBe('2018-01-01')
})
})
I get this error:
created only runs 1 time when component is created.
When you use setProps, component props will be updated but created method will not be called again.
import { mount } from 'vue-test-utils'
import SelecionadorData from '#/components/Shared/SelecionadorData.vue'
describe('SelecionadorData.vue', () => {
it('should receive the props datainicial', () => {
const wrapper = mount(SelecionadorData,
{
propsData: {
datainicial: '2018-01-01'
}
})
expect(wrapper.vm.date).toBe('2018-01-01')
})
})
Related
I'm trying to test if a composable's method is called from my store. Here's what I have:
/stores/ui:
import { defineStore } from 'pinia';
import { useUtils } from '#/composables/utils';
const { sendEvent } = useUtils();
interface State {
tab: string,
}
export const useUIStore = defineStore('ui', {
state: (): State => ({
tab: 'Home',
}),
actions: {
setTab(tabName: string) {
this.tab = tabName;
sendEvent('navigation', `click_${tabName}`);
},
}
})
#/composables/utils:
export function useUtils() {
const sendEvent = (name: string, value: string) => {
// do stuff
};
return {
sendEvent
};
}
And here's my test file:
import { setActivePinia, createPinia } from 'pinia'
import { useUIStore } from '#/stores/ui';
import { useUtils } from '#/composables/utils';
describe('', () => {
let uiStore;
beforeEach(() => {
setActivePinia(createPinia());
uiStore = useUIStore();
})
it('Sets the active tab', () => {
let sendEvent = jest.spyOn(useUtils(), 'sendEvent');
expect(uiStore.tab).toBe('Home');
uiStore.setTab('help');
expect(uiStore.tab).toBe('help');
expect(sendEvent).toHaveBeenCalledTimes(1);
});
})
I've also tried mocking the import:
jest.mock(
'#/composables/utils',
() => {
function useUtils() {
return {
sendEvent: jest.fn(),
}
}
return {
useUtils
};
},
{ virtual: true },
);
import { useUtils } from '#/composables/utils';
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
What's the correct way of adding a spy to sendEvent ?
In your test file - mock the utils:
const mockSendEvent = jest.fn();
jest.mock("#/composables/utils", () => ({
useUtils: () => ({
sendEvent: mockSendEvent,
}),
}));
and then update your test to use the mock:
it('Sets the active tab', () => {
expect(uiStore.tab).toBe('Home');
uiStore.setTab('help');
expect(uiStore.tab).toBe('help');
expect(mockSendEvent).toHaveBeenCalledTimes(1);
});
I've been working in a test where I need the data from Vuex. However, I'm having some problems, I need to change that data in each test in order to test the functionality of the component.
Here is my component:
<template>
<div id="cb-items-displayer" #click="textClick">
<span>(</span>
<p>{{ text }}</p>
<span>)</span>
</div>
</template>
<script lang="ts" setup>
import { capitalize } from '#/utils/capitalize'
import { ItemsDisplayer } from '#/models/ItemsDisplayer'
import { computed, PropType } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const props = defineProps({
type: {
type: String,
default: '',
},
menuType: {
type: String,
default: '',
},
items: {
type: Array as PropType<ItemsDisplayer[]>,
default: () => [],
}
})
const emit = defineEmits<{
(event: 'textClicked'): void
}>()
const text = computed(() => {
const param = props.menuType === 'radio' ? 'One' : 'Many'
console.log( "TYPEEE ", props.type, " ", param )
const itemsIds = store.getters['filters/get' + capitalize(props.type) + param]
console.log("ITEMSSS", JSON.stringify(itemsIds))
return getTextToShow(itemsIds)
})
const getTextToShow = (itemsIds: string) => {
//TODO - improve it
if (itemsIds === 'all') {
return 'all'
} else if (itemsIds.length === 0) {
return '-'
} else if (itemsIds.length === 1) {
return getName(itemsIds[0], props.items)
} else {
return itemsIds.length
}
}
const textClick = () => {
emit('textClicked')
}
const getName = (id: string, items: ItemsDisplayer[]) => {
const found: ItemsDisplayer = items.find((x) => x.id! === id) as ItemsDisplayer
console.log("GETNAME ", found.name)
return found?.name
}
</script>
And this is the test:
import { render, screen, click, waitFor } from '#tests/app-test-utils'
import ItemsDisplayer from './ItemsDisplayer.vue'
import { capitalize } from '#/utils/capitalize'
let mockStoreCommit: jest.Mock
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: [],
},
commit: mockStoreCommit,
}),
}))
describe('ItemsDisplayer', () => {
beforeEach(() => {
mockStoreCommit = jest.fn()
render(
ItemsDisplayer,
{},
{
props: {
type: 'categories',
menuType: 'checkbox',
items: [
{
box_templates:"",
id:"1",
name:"Culture"
},
{
box_templates:"",
id:"2",
name:"Economy"
},
{
box_templates:"",
id:"3",
name:"Education"
}
]},
}
)
})
it('renders the component', async() => {
await screen.getByText('-')
})
it('renders the component with one item', async() => {
//DON'T WORK HERE THERE SHOULD BE A CHANGE OF DATA IN THE MOCKED STORE IN ORDER TO WORK
await screen.getByText('Culture')
})
})
My problem is that I need to change the value of [filters/get${capitalize('categories')}Many] to ["1"] in the second test.
I tried several things in order to change the mocked data but they don't work. How can I change the mocked store data in each test?
Thanks!
You can achieve this by lazy loading your vue component:
Add jest.resetModules(); in the beforeEach to reset all of the imported modules before each test so they can be re-evaluated and re-mocked:
beforeEach(() => {
jest.resetModules();
In each unit test, you will first need to import the vue component using the require syntax as follows:
const ItemsDisplayer = require('./ItemsDisplayer.vue').default;
Then add the mock directly after the import with the [`filters/get${capitalize('categories')}Many`] value being set to whatever you want it to be:
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: ["1"],
},
commit: mockStoreCommit,
}),
}));
I have noticed that you do your rendering in the beforeEach. Unfortunately because you import and mock your modules during the test, the rendering will need to be done after these have taken place - hence you will need to either move that logic into your unit test or extract it into another function which can be called from within the unit test.
Each unit test should look something like this:
it('renders the component', async() => {
const ItemsDisplayer = require('./ItemsDisplayer.vue').default;
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: ["1"],
},
commit: mockStoreCommit,
}),
}));
// beforeEach logic here or a call to a function that contains it
await screen.getByText('-')
})
I am trying to watch for some warnings in my component
import VueCompositionApi, { watch } from '#vue/composition-api';
import useWarning from '#composables/warnings';
Vue.use(VueCompositionApi);
setup () {
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
});
}
In my composition function I just push into the reactive array to simulate warning.
import { reactive } from '#vue/composition-api';
export default function useWarnings () {
const activeWarnings = reactive([]);
setInterval(() => {
fakeWarning();
}, 3000);
function fakeWarning () {
activeWarnings.push({
type: 'severe',
errorCode: 0,
errorLevel: 'red',
}
);
}
return { activeWarnings };
Does this not work in Vue 2 at all? Is there a workaround? activeWarnings does update in my component - I see the array filling up but this watcher is never called.
I am using https://composition-api.nuxtjs.org/
Since activeWarnings is an array you should add immediate:true option :
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});
or
const { activeWarnings } = useWarning();
watch(()=>activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});
But i recommend the following syntax :
import { reactive,toRef } from '#vue/composition-api';
export default function useWarnings () {
const state= reactive({activeWarnings :[]});
setInterval(() => {
fakeWarning();
}, 3000);
function fakeWarning () {
state.activeWarnings.push({
type: 'severe',
errorCode: 0,
errorLevel: 'red',
}
);
}
return { activeWarnings : toRef(state,'activeWarnings')};
then :
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});
I'm trying to make a test for dynamically created methods in one of my components the code goes like this.
<template>
<div id="app">
<div #click="executeDynamic('myCustomFunction')">Click me!</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
// These contain all dynamic user functions
userFuncs: {}
}
},
created () {
window.setTimeout(() => {
this.$set(this.userFuncs, 'myCustomFunction', () => {
console.log('whoohoo, it was added dynamically')
})
}, 2000)
},
methods: {
executeDynamic (name) {
if (this.userFuncs[name]) {
this.userFuncs[name]()
} else {
console.warn(`${name} was not yet defined!`)
}
}
}
};
</script>
test file
import WorkDateTime from "#/components/WorkDateTime.vue"
import Vue from "vue"
describe("WorkDateTime.vue", () => {
it("allowedDatesFrom: today -> NG", () => {
const that = {
$set: Vue.set
}
expect(WorkDateTime.data.userFuncs['myCustomFunction']).toBeTruthy()
})
}
code pen
https://codesandbox.io/s/vue-template-forked-ec7tg?file=/src/App.vue:0-662
Try something like that:
import { shallowMount } from '#vue/test-utils';
import WorkDateTime from '#/components/WorkDateTime.vue';
describe('WorkDateTime.vue', () => {
it('userFuncs empty', () => {
let wrapper = shallowMount(WorkDateTime);
expect(wrapper.vm.userFuncs).toStrictEqual({});
});
it('userFuncs filled', async () => {
let wrapper = shallowMount(WorkDateTime);
let wait3Seconds = () => new Promise(resolve => setTimeout(() => resolve(), 3000));
await wait3Seconds();
expect(wrapper.vm.userFuncs['myCustomFunction']).toBeInstanceOf(Function);
});
});
After user login authentication ( LoginPage component ) the currentUserId is set in the store, but trying to get it later in another component ( ShoppingLists ) gives an undefined value ... what's wrong with my flow ?
here is my store.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from '#/vuex/getters'
import actions from '#/vuex/actions'
import mutations from '#/vuex/mutations'
import vueAuthInstance from '../services/auth.js'
Vue.use(Vuex)
const state = {
shoppinglists: [],
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
Here are the console.log output with related pieces of code
// LoginPage component submit button fires the login action
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
action.js
login: ({ commit }, payload) => {
payload = payload || {}
return vueAuthInstance.login(payload.user, payload.requestOptions)
.then((response) => {
// check response user or empty
if (JSON.stringify(response.data) !== '{}') {
commit(IS_AUTHENTICATED, { isAuthenticated: true })
commit(CURRENT_USER_ID, { currentUserId: response.data.id })
return true
} else {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return false
}
})
},
console.log
mutations.js?d9b0:23
state isAuthenticated: true
mutations.js?d9b0:27
committed state currentUserId: 1
At this point the store should be updated ....
// then the LoginPage component push the ShoppingListsPage
when mounted it shoudl populates the shoppinglists
methods: _.extend({}, mapActions(['populateShoppingLists', 'createShoppingList']), {
addShoppingList () {
let list = { title: 'New Shopping List', items: [] }
this.createShoppingList(list)
}
}),
store,
mounted: function () {
this.$nextTick(function () {
console.log('GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER')
this.populateShoppingLists()
})
}
console.log
ShoppingListsPage.vue?88a1:52
GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER
actions.js?a7ea:9
TRYING TO GET currentUserId with GETTERS
populateShoppingLists: ({ commit }) => {
console.log('TRYING TO GET currentUserId with GETTERS')
const currentUserId = getters.getCurrentUserId({ commit })
console.log('ACTIONS: populateShoppingLists for user: ', currentUserId)
return api.fetchShoppingLists(currentUserId)
.then(response => {
commit(POPULATE_SHOPPING_LISTS, response.data)
return response
})
.catch((error) => {
throw error
})
},
console.log
getters.js?d717:9
GETTERS: currentUserId: undefined
Getters returning an undefined value from the store
getCurrentUserId: (state) => {
console.log('GETTERS: currentUserId: ', state.currentUserId)
return state.currentUserId
},
UPDATE
mutations.js
import * as types from './mutation_types'
import getters from './getters'
import _ from 'underscore'
export default {
[types.POPULATE_SHOPPING_LISTS] (state, lists) {
state.shoppinglists = lists
},
[types.IS_AUTHENTICATED] (state, payload) {
console.log('committed state isAuthenticated: ', payload.isAuthenticated)
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
console.log('committed state currentUserId: ', payload.currentUserId)
state.currentUserId = payload.currentUserId
}
}
mutation_types
export const POPULATE_SHOPPING_LISTS = 'POPULATE_SHOPPING_LISTS'
export const IS_AUTHENTICATED = 'IS_AUTHENTICATED'
export const CURRENT_USER_ID = 'CURRENT_USER_ID'
I solved the issue , modifying the action populateShoppingLists
Need to pass the state as a parameter with the commit , so I can use the getters inside my action
populateShoppingLists: ({ commit, state }) => {
let currentUserId = getters.currentUserId(state)
console.log('currentUserId: ', currentUserId). // => userId: 1 Ok
return api.fetchShoppingLists(currentUserId)