I am learning about unit-testing in Vue and try to test component according to this article https://alligator.io/vuejs/testing-vue-with-jest/
I have a component
<template>
<div>
<h1 :style="headingStyles">{{title}}</h1>
</div>
</template>
<script>
export default {
data() {
return {
headingStyles: {
color: this.color
}
};
},
props: ["title", "color"]
};
</script>
and file with test cases
import Vue from 'vue';
import FancyHeading from '../../src/components/FancyHeading';
function mountComponentWithProps (Component, propsData) {
console.log('props Data', propsData)
const Constructor = Vue.extend(Component);
const vm = new Constructor({
propsData
}).$mount();
return vm.$el;
}
describe('FancyHeading.vue', () => {
it('should be the correct color', () => {
const headingData = mountComponentWithProps(FancyHeading, { color: 'blue' });
const styleData = headingData.style.getPropertyValue('color');
console.log(styleData)
expect(styleData).toEqual('blue');
});
it('should have the correct title', () => {
const headingData = mountComponentWithProps(FancyHeading, { title: 'Hello, Vue!' });
const titleData = headingData.textContent;
expect(titleData).toEqual('Hello, Vue!');
});
});
When I run yarn test:unit I receive error
FancyHeading.vue › should be the correct color
expect(received).toEqual(expected) // deep equality
Expected: "blue"
Received: ""
Looks like color is empty xtring, but I don't understand why. Can someone explain me and help to pass test?
Related
I have a fresh project using vue3 and Element-plus.
I have this component that uses el-table
<template>
<el-table :data="data">
<el-table-column label="id">
<template #default="scope">
<el-button>
{{ scope.row.id }} // This line is failing the test.
</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
const data = [{ id: 1 }, { id: 2 }, { id: 3 }]
</script>
And a simple test:
import { describe, it, expect } from 'vitest'
import { mount } from '#vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toBeTruthy()
})
})
The thing is, using the default slot on the el-table-column and printing directly on the DOM with scope variable is failing the test with this error message:
TypeError: Cannot read properties of undefined (reading 'row')
How can I mock this? I tried doing this:
import { describe, it, expect } from 'vitest'
import { mount } from '#vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
const scope = () => {
return {
row: {}
}
}
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, {
global: {
mocks:{
scope
}
}
})
expect(wrapper.text()).toBeTruthy()
})
})
But seems it's ignoring this mock...
The interesting this is, if I use this scope variable in something else (not printing in the DOM) the test pass, like this:
<template>
<el-table :data="data">
<el-table-column label="id">
<template #default="scope">
<el-button #click="handleClick(scope.$index, scope.row)">
Test
</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
const data = [{ id: 1 }, { id: 2 }, { id: 3 }]
</script>
Well... how can I solve this problem?
Thank you
I am using Nuxt.js with Jest for unit testing. I added a head function in my layout to change the title and I would like to unit test it.
Here is my file:
<template>
<h1 class="title">HELLO</h1>
</template>
<script>
export default {
data () {
return {
title: 'MY TITLE'
}
},
head () {
return {
title: this.title,
meta: [
{ hid: 'description', name: 'description', content: 'MY DESCRIPTION' }
]
}
}
}
</script>
I tried:
const wrapper = shallowMount(index)
wrapper.vm.head() <- fails
Any suggestions?
Inject vue-meta plugin in the Vue instance used for mounting the component. You can then access head() data with wrapper.vm.$metaInfo. See example below.
pageOrLayoutToTest.vue
<template>
<h1 class="title">HELLO</h1>
</template>
<script>
export default {
data () {
return {
title: 'MY TITLE'
}
},
head () {
return {
title: this.title,
meta: [
{ hid: 'description', name: 'description', content: 'MY DESCRIPTION' }
]
}
}
}
</script>
pageOrLayoutToTest.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils'
import VueMeta from 'vue-meta'
// page or layout to test
import pageOrLayoutToTest from '#/path/to/pageOrLayoutToTest.vue'
// create vue with vue-meta
const localVue = createLocalVue()
localVue.use(VueMeta, { keyName: 'head' })
describe('pageOrLayoutToTest.vue', () => {
let wrapper;
// test set up
beforeEach(() => {
wrapper = shallowMount(pageOrLayoutToTest, {
localVue
})
})
// test tear down
afterEach(() => {
if (wrapper) {
wrapper.destroy()
}
})
it('has correct <head> content', () => {
// head data injected by the page or layout to test is accessible with
// wrapper.vm.$metaInfo. Note that this object will not contain data
// defined in nuxt.config.js.
// test title
expect(wrapper.vm.$metaInfo.title).toBe('MY TITLE')
// test meta entry
const descriptionMeta = wrapper.vm.$metaInfo.meta.find(
(item) => item.hid === 'description'
)
expect(descriptionMeta.content).toBe('MY DESCRIPTION')
})
})
I have created a Vue component and a unit test to validate its behavior. I can use the component on my Vue app without any issues, but when I run my unit test using jest, I get the error:
error TS2339: Property 'extend' does not exist on type 'typ
eof import("</path/to/module>")'.
1 export default Vue.extend({
vButton.ts - Component under test
export default Vue.extend({
template: `
<div>
<button class="default-button" #click="click">
<span>{{ text }}</span>
</button>
</div>
`,
props: {
text: String,
action: Function
},
methods: {
click(): void {
this.$emit('action');
}
}
});
vButton.spec.ts - unit test
import { mount } from '#vue/test-utils'
import vButton from '../views/core/ts/components/vButton'
describe('vButton', () => {
describe(':props', () => {
it(':text - should render a button with the passed-in label text', () => {
const msg = 'new message';
const wrapper = mount(vButton, {
propsData: { text: msg },
});
expect(wrapper.text()).toMatch(msg);
});
});
describe('#events', () => {
it('#click - should emit an "action" event when the button is clicked', () => {
const wrapper = mount(vButton);
const button = wrapper.find('button')
button.trigger('click')
expect(wrapper.emitted().action).toBeTruthy()
})
});
});
I expect Vue.extend to work fine when running the unit test.
I'm trying to unit test a component method. The question here does not lay out how to access the component method from a unit test.
Specifically, given my Vue component below, how do I access doSomeWork() from my unit test?
Vue component:
<template>
<div id="ThisStuff">
<span>
Some other stuff is going on here
</span>
</div>
</template>
<script>
import foo from 'bar'
export default {
props: {
ObjectWithStuffInIt: [
{
id: 1
bar: false
},
{
id: 2
bar: false
},
]
},
data: {
foo: "foo"
},
methods: {
doSomeWork: function() {
for (var i = 0; i < ObjectWithStuffInIt.length; i++) {
if (foo === "diddly") {
ObjectWithStuffInIt[i].bar = true;
}
}
}
}
}
</script>
My test code:
import {createLocalVue, shallow} from 'vue-test-utils'
import ThisVueFile.test.js from '../../thisPlace/ThatPlace/ThisVueFile.vue'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex);
describe('ThisVueFile.test.js', () => {
let user;
let store;
beforeEach(() => {
let getters = {
user: () => user
}
store = new Vuex.Store({ getters })
})
// I need to fill propsData: with some local data here
// because it is server data
// I need to have access to the method
// I need to use local data for `foo` in the test.
it(' When foo is set to -diddly- then set bar to true ', () => {
foo = "diddly";
// run the method in the component here
doSomeWork();
expect(OjbectWithStuffInIt[0].bar.equals(true));
})
})
Calling component method
The wrapper provides access to the component instance via its vm property, so you could call the method directly with:
wrapper.vm.doSomeWork()
Setting props
The mounting options (passed to shallowMount() or mount()) include the propsData property that could be used to initialize the component's props before mounting.
You could also use the wrapper's setProps() after the component has already been mounted.
Example:
it('...', () => {
const wrapper = shallowMount(MyComponent, {
propsData: {
myItems: [
{ id: 200, bar: false },
{ id: 300, bar: false }
]
}
});
// OR
wrapper.setProps({
myItems: [
{ id: 400: bar: true }
]
})
})
Modifying component data property
The mounting options includes the data property that could be used to initialize the component's data before mounting.
You could also use the wrapper's setData() after the component has already mounted.
You could access the component's data property directly through the wrapper's vm property.
Example:
it('...', () => {
const wrapper = shallowMount(MyComponent, {
data() {
return {
foo: 1
}
}
});
// OR
wrapper.setData({ foo: 2 })
// OR
wrapper.vm.foo = 3
})
Full example
Altogether, your test might look similar to this:
import { createLocalVue, shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent'
describe('MyComponent', () => {
it('When foo is set to -something-, set bar to true', () => {
const myItems = [
{ id: 200, bar: false },
{ id: 300, bar: false }
]
const localVue = createLocalVue()
const wrapper = shallowMount(MyComponent, {
localVue,
propsData: {
myItems
}
})
wrapper.vm.foo = 'something'
wrapper.vm.doSomeWork()
expect(myItems[0].bar).toBe(true)
})
})
demo
I am trying to test the following component w Avoriaz, but upon props change , the action in watch: {} is not triggered
ItemComponent.vue
switch checkbox
✗ calls store action updateList when item checkbox is switched
AssertionError: expected false to equal true
at Context.<anonymous> (webpack:///test/unit/specs/components/ItemComponent.spec.js:35:47 <- index.js:25510:48)
thanks for feedback
ItemComponent.vue
<template>
<li :class="{ 'removed': item.checked }">
<div class="checkbox">
<label>
<input type="checkbox" v-model="item.checked"> {{ item.text }}
</label>
</div>
</li>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: ['item', 'id'],
methods: mapActions(['updateList']),
watch: {
'item.checked': function () {
this.updateList(this.id)
}
}
}
</script>
here is my component test
ItemComponent.spec.js
import Vue from 'vue'
import ItemComponent from '#/components/ItemComponent'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('ItemComponent.vue', () => {
let actions
let store
beforeEach(() => {
actions = {
updateList: sinon.stub()
}
store = new Vuex.Store({
state: {},
actions
})
})
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', () => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
expect(actions.updateList.calledOnce).to.equal(true)
})
})
})
U mistaked the prop,use :checked instead
I should write my expect(actions.updateList() . within a $nextTick block
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', (done) => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item.checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
wrapper.find('input')[0].trigger('input')
wrapper.vm.$nextTick(() => {
expect(actions.updateList.calledOnce).to.equal(true)
done()
})
})
})
then my test is OK
ItemComponent.vue
switch checkbox
✓ calls store action updateList when item checkbox is switched