key.charAt is not a function error in createLocalVue() - vuejs2

Using createLocalVue function throws a TypeError: key.charAt is not a function. The interesting thing is that it is thrown only in the case of some components while I use localVue in every components tests. Here is a sample test code:
import assign from 'lodash/assign'
import { mount, createLocalVue } from '#vue/test-utils'
import filters from '#/filters'
import { DateUtils } from '#/filters/date-utils'
import TaskCard from '#/components/molecules/TaskCard'
import Icon from '#/components/atoms/Icon'
const localVue = createLocalVue()
describe('TaskCard.vue', () => {
let wrapper
const task = {
Status: 3,
Name: 'Sample task',
Deadline: Date.now(),
Owner: {
DisplayName: 'Admin'
},
Title: 'Sample title'
}
const position = 2
beforeEach(() => {
wrapper = mount(TaskCard, {
localVue,
propsData: {
task,
position
}
})
})
it('TaskCard should be a Vue instance and should have a div root element with the class "ca-task-card"', () => {
expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('div')).toBe(true)
expect(wrapper.classes()).toContain('ca-task-card')
})
it('TaskCard\'s ca-task-order element should have the mocked status prop', () => {
expect(wrapper.find('.ca-task-order').find('span').text()).toBe(position.toString())
})
it('TaskCard\'s first "p" element should contain the modified task name as text', () => {
const newTask = assign(task, { Name: 'Sample task name' })
wrapper.setProps({task: newTask})
expect(wrapper.findAll('p').at(0).text()).toBe('Sample task name')
})
it('TaskCard should have 4 Icon component children', () => {
expect(wrapper.findAll(Icon).length).toBe(4)
})
it('TaskCard should have the formatted deadline of the task in the element with the class "ca-task-card__deadline"', () => {
expect(wrapper.find('.ca-task-card__deadline').text()).toBe(DateUtils.formatDate(wrapper.vm.task.Deadline))
})
it('TaskCard with completed status should have completed icon and not any others', () => {
expect(wrapper.find('.ca-task-card__icon--completed').exists()).toBe(true)
expect(wrapper.find('.ca-task-card__icon--delegated').exists()).toBe(false)
})
it('TaskCard with delegated status should have delegated icon and not any others', () => {
const newTask = assign(task, {
Status: 2,
Name: 'Sample delegated task'
})
const newWrapper = mount(TaskCard, {
localVue,
propsData: {
task: newTask,
position
}
})
expect(newWrapper.find('.ca-task-card__icon--completed').exists()).toBe(false)
expect(newWrapper.find('.ca-task-card__icon--delegated').exists()).toBe(true)
})
})
Here is the full stacktrace:
FAIL test\unit\specs\molecules\TaskCard.spec.js
● Test suite failed to run
TypeError: key.charAt is not a function
6 | import Icon from '#/components/atoms/Icon'
7 |
> 8 | const localVue = createLocalVue()
9 |
10 | describe('TaskCard.vue', () => {
11 | let wrapper
at Object.has (node_modules/vue/dist/vue.common.js:1932:50)
at baseGetTag (node_modules/#vue/test-utils/dist/vue-test-utils.js:448:48)
at baseClone (node_modules/#vue/test-utils/dist/vue-test-utils.js:5047:15)
at node_modules/#vue/test-utils/dist/vue-test-utils.js:5086:31
at arrayEach (node_modules/#vue/test-utils/dist/vue-test-utils.js:4304:9)
at baseClone (node_modules/#vue/test-utils/dist/vue-test-utils.js:5080:3)
at node_modules/#vue/test-utils/dist/vue-test-utils.js:5086:31
at arrayEach (node_modules/#vue/test-utils/dist/vue-test-utils.js:4304:9)
at baseClone (node_modules/#vue/test-utils/dist/vue-test-utils.js:5080:3)
at node_modules/#vue/test-utils/dist/vue-test-utils.js:5086:31
at arrayEach (node_modules/#vue/test-utils/dist/vue-test-utils.js:4304:9)
at baseClone (node_modules/#vue/test-utils/dist/vue-test-utils.js:5080:3)
at cloneDeep (node_modules/#vue/test-utils/dist/vue-test-utils.js:5116:10)
at node_modules/#vue/test-utils/dist/vue-test-utils.js:5141:11
at Array.forEach (<anonymous>)
at createLocalVue (node_modules/#vue/test-utils/dist/vue-test-utils.js:5137:20)
at Object.<anonymous> (test/unit/specs/molecules/TaskCard.spec.js:8:46)

Related

how to check if a method has been called in a lifecycle hook (JEST + VUE)?

Okey, I have some test method and lifecycle hook
testMethod(){
console.log('test')
}
beforeMount() {
this.testMethod()
},
test
import {createLocalVue, shallowMount} from "#vue/test-utils"
import TestComponents from '../../assets/components/TestComponents'
const localVue = createLocalVue()
describe('test component TestComponents', () => {
let wrapper = shallowMount(TestComponents,
{
localVue
})
test('beforeMount hook', async () => {
let testMethodSpy = jest.spyOn(wrapper.vm, 'testMethod')
expect(testMethodSpy).toBeCalled()
})
})
Why is the method called(in command line "console.log test") but the validation returns an error?
enter image description here
enter image description here

Jest: How I should change the mock data of Vuex in each test?

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('-')
})

Vue JS JEST test

I have a strange behaviour when trying to run a JEST unit test.
import { shallowMount } from '#vue/test-utils'
import communicationPreferences from '#/pages/account/communication-preferences'
describe('Communication Preferences Page', () => {
it('should render page', () => {
const wrapper = shallowMount(communicationPreferences)
expect(wrapper.exists()).toBe(true)
})
})
Page: communication-preferences.vue
computed: {
...mapState('account', ['communicationPreferences']),
// communicationPreferenceTypeEmail() {
// return this.communicationPreferences.filter((e) => e.type === 'EMAIL')
// },
// communicationPreferenceTypeNotEmail() {
// return this.communicationPreferences.filter((e) => e.type !== 'EMAIL')
// },
},
When I run npm run test with computed lines above uncomment I get the error below but when I comment them out I have a successful test pass.
TypeError: Cannot read property 'state' of undefined
127 | ...mapState('account', ['communicationPreferences']),
128 | communicationPreferenceTypeEmail() {
> 129 | return this.communicationPreferences.filter((e) => e.type === 'EMAIL')
| ^
130 | },
131 | communicationPreferenceTypeNotEmail() {
132 | return this.communicationPreferences.filter((e) => e.type !== 'EMAIL')
Cannot read property 'state' of undefined but I don't understand why, anything obvious I am missing here?
This happens when trying mapState (or other mapXXX Vuex utilities) without an initialized Vuex store.
Solution
One way to fix this is to pass in the store via the global.plugins mounting option:
import { shallowMount } from '#vue/test-utils'
import communicationPreferences from '#/pages/account/communication-preferences'
import store from '#/store'
describe('Communication Preferences Page', () => {
it('should render page', () => {
const wrapper = shallowMount(communicationPreferences,
{ 👇
global: {
plugins: [store],
},
}
)
expect(wrapper.exists()).toBe(true)
})
})
demo

React Native/Jest TypeError: Cannot read property 'params' of undefined - testing with jest

I'm trying to create a test in an application with jest and this is some lines of my code:
import React, { Component } from 'react';
import {...} from 'react-native';
import jwt_decode from 'jwt-decode';
class CreateProduct extends Component {
constructor(props) {
super(props);
this.keyboardHeight = new Animated.Value(0);
this.imageHeight = new Animated.Value(199);
this.state = {
isButtonsHidden: false,
title: '',
price: '',
description: '',
isDialogVisible: false,
messageError: '',
};
}
_goBack = async () => {
const {state} = this.props.navigation;
var token = state.params ? state.params.token : undefined;
this.props.navigation.navigate('MyProducts', {token:token});
}
I want to test the navigation:
this.props.navigation.navigate('MyProducts', {token:token});
Now this is the attempt to test:
describe('Testing navigation', () =>{
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation:{
navigate: spyNavigate
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props}/>)
wrapper.setState({params: params})
})
it('should test navigation', () => {
wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
But I'm receiving this error.
I'm assuming that there is an error with the way I'm passing the const params. Can you help me telling what's the best way I can do this to simulate a token and that way I can navigate in the screen?
Thanks.
Rootcause is your _goBack is async. But you don't await till it ends before running expect. Even more: jest also does not wait _goBack to finish so you don't even see an error
Cannot read property 'params' of undefined
that happens because you don't mock state in navigation.params.
To work with async code there are 2 different approaches in Jest: either returning Promise from the it() or running done() callback manually(it's passed as 1st argument in it()).
I'll picking 2nd since it allows us also await until goBack is finished before running expect:
describe('Testing navigation', () => {
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation: {
navigate: spyNavigate,
state: {}
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props} />)
wrapper.setState({ params: params })
})
it('should test navigation', async () => {
await wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
Or without using async/await it would look like
it('should test navigation', () => {
return wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled());
})
that looks messy
Or using done() callback
it('should test navigation', (done) => {
wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled()).
then(done);
})

Test Error Comparing Props and Data

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')
})
})