I am trying to test the following part of my Heading.vue component
<v-layout column align-center justify-center>
<img src="#/assets/images/logo.png" alt="Logo-Choro-des-Charentes" height="200">
<h1 class="mb-2 display-1 text-xs-center">{{ $t('lang.views.home.heading.header1') }}</h1>
<h2 class="mb-2 display-1 text-xs-center"> {{ $t('lang.views.home.heading.header2') }}</h2>
<div class="subheading mb-3 text-xs-center">{{ $t('lang.views.home.heading.subheading') }}</div>
<v-btn v-if="!listening" id="playBtn" round #click="listening = true" class="primary" large href="#">{{ $t("lang.views.home.heading.btn__listen") }}
<v-icon right>play_arrow</v-icon>
</v-btn>
<v-btn v-else round large href="#">
<audioplayer id="audioplayer" :autoplay="true" :loop="false" :sources="audioSources" #playerStop="{{ listening = false; }}"></audioplayer>
</v-btn>
</v-layout>
using the follwoing spec file
import Vue from "vue";
import { shallowMount } from "#vue/test-utils";
import router from "#/router";
import Vuetify from "vuetify";
import i18n from "#/locales";
import Heading from "#/components/Home/Heading.vue";
describe("Heading.vue", () => {
let wrapper;
beforeEach(() => {
Vue.use(router);
Vue.use(Vuetify);
Vue.filter("translate", function(value) {
if (!value) return "";
value = "lang.views.global." + value.toString();
return i18n.t(value);
});
const el = document.createElement("div");
el.setAttribute("data-app", true);
document.body.appendChild(el);
});
it("should display AUDIOPLAYER on event LISTEN link click", (done) => {
wrapper = shallowMount(Heading, { router, i18n });
wrapper.find("#playBtn").trigger("click");
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.listening).toBe(true);
done();
});
});
});
But I get a Timeout error .... so test is failing
RUNS tests/unit/Heading.spec.js
Test Suites: 1 passed, 1 of 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9s, estimated 10s
console.error node_modules/vue/dist/vue.runtime.common.js:589
[Vue warn]: Error in nextTick: "Error: expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false"
found in
---> <Heading>
<Root>
console.error node_modules/vue/dist/vue.runtime.common.js:1739
{ Error: expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
at VueComponent.toBe (/Users/yves/Developments/WIP/VUE.JS-cli-3/3-chocha-home-content/chocha/tests/unit/Heading.spec.js:37:36)
at Array.<anonymous> (/Users/yves/Developments/WIP/VUE.JS-cli-3/3-chocha-home-content/chocha/node_modules/vue/dist/vue.runtime.common.js:1835:12)
at flushCallbacks (/Users/yves/Developments/WIP/VUE.JS-cli-3/3-chocha-home-content/chocha/node_modules/vue/dist/vue.runtime.common.js:1756:14)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
FAIL tests/unit/Heading.spec.js (9.708s)
Heading.vue
✕ should display AUDIOPLAYER on event LISTEN link click (5191ms)
● Heading.vue › should display AUDIOPLAYER on event LISTEN link click
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
31 | });
32 | */
> 33 | it("should display AUDIOPLAYER on event LISTEN link click", (done) => {
| ^
34 | wrapper = shallowMount(Heading, { router, i18n });
35 | wrapper.find("#playBtn").trigger("click");
36 | wrapper.vm.$nextTick(() => {
at Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:85:20)
at Suite.it (tests/unit/Heading.spec.js:33:3)
at Object.describe (tests/unit/Heading.spec.js:8:1)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 2 passed, 3 total
Snapshots: 0 total
Time: 14.43s
Ran all test suites matching /Heading.spec.js/i.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
It's fine when I use #click.native event
<v-btn v-if="!listening" id="playBtn" round #click.native="listening = true" class="primary" large href="#">{{ $t("lang.views.home.heading.btn__listen") }}
<v-icon right>play_arrow</v-icon>
</v-btn>
it("should display AUDIOPLAYER on event LISTEN link click", () => {
// given
wrapper = shallowMount(Heading, { router, i18n });
// when
wrapper.find('#playBtn').trigger('click');
// then
expect(wrapper.vm.listening).toEqual(true);
});
Related
I am a little puzzled I am trying to unit test a child (ratingsItem component) emitting to a parent component. Here is the test code:
it("rating component should emit call to parent updateRating function", () => {
const wrapper = factory({});
const ratingComp = wrapper.find({ref: "ratings"});
ratingComp.vm.$emit("updateRating", 1);
expect(wrapper.emitted().updateRating).toEqual(1);
expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
});
This is the code inside the parent component. You can see the ratingsItem component inside it:
<template>
<div>
<div class="overlay" v-show="isModalOpen" #click="closeModal"></div>
<div ref="modal" class="modal" v-show="isModalOpen">
<div slot="header" class="modal_header">
<div class="modal_header_title">{{headerTitle}}</div>
<div #click="closeModal" class="modal_header_close">
<img src="../assets/modal_close_icon.svg" />
</div>
</div>
<div>
<label>Restaurant Rating</label>
<!-- rating component -->
<ratingsItem ref="ratings" :rating="rating" :starSizePerc="12" #updateRating="updateRating"></ratingsItem>
</div>
<!-- footer -->
<button class="pointer cancel_btn" #click="closeModal">Cancel</button>
<button class="pointer space_left save_btn" #click="submitDetails()">Save</button>
</div>
</div>
</template>
But when I run the test I get this error:
expect(received).toEqual(expected) // deep equality
Expected: 1
Received: undefined
71 |
72 |
> 73 | expect(wrapper.emitted().updateRating).toEqual(1);
| ^
74 | // expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
75 | });
76 | });
Can anyone help?
I checked vue-test-utils docs for 'emitted':
https://vue-test-utils.vuejs.org/api/wrapper/emitted.html
Can you try to wrap it in vm.$nextTick:
it("rating component should emit call to parent updateRating function", (done) => {
const wrapper = factory({});
const ratingComp = wrapper.find({ref: "ratings"});
ratingComp.vm.$emit("updateRating", 1);
wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().updateRating).toEqual(1);
expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
done()
})
});
I figured it out this is what I used below:
it("rating component should emit call to parent updateRating function", () => {
const wrapper = factory({});
const updateRating = jest.fn();
wrapper.setMethods({ updateRating });
wrapper.find({ ref: "ratings" }).vm.$emit("updateRating", { idx: 9 });
expect(updateRating).toHaveBeenCalled();
expect(updateRating).toHaveBeenCalledWith({ idx: 9 });
});
Thanks for your help with this :)
I'm trying to do a unit test on a Vue component. I'm getting an error when wrapper.find() is used.
Component is as given below:
snackbar.vue
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
visibility: "snackbar/visibility",
type: "snackbar/type",
message: "snackbar/message"
})
},
watch: {
visibility(value) {
if (value) {
$("#snackbar").addClass("show " + this.type);
setTimeout(() => {
$("#snackbar").removeClass("show " + this.type);
this.$store.dispatch("snackbar/close");
}, 3000);
}
}
}
};
</script>
<template>
<div id="snackbar">{{ message }}</div>
</template>
In the testing I want to get a div having snackbar as id using wrapper.find().
It's spec file:
snackbar.spec.js
import SnackBar from '../../../src/modules/Common/_components/snackbar.vue';
import { mount, createLocalVue } from '#vue/test-utils';
import Vue from 'vue'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Snackbar component', () => {
let store
beforeEach(() => {
let state = {
isVisible: false,
message: '',
type: ''
}
let getters = {
'snackbar/visibility': (state) => state.isVisible,
'snackbar/type': (state) => state.type,
'snackbar/message': (state) => state.message
}
store = new Vuex.Store({
modules: {
snackbar: {
state,
getters
}
}
})
})
it('renders the correct markup', () => {
let wrapper = mount(SnackBar, { localVue, store })
let snackbar = wrapper.find('#snackbar');
// some test code related to snackbar
})
})
The log is as given below:
cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
30 05 2018 18:18:57.847:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
30 05 2018 18:18:57.849:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
30 05 2018 18:18:57.855:INFO [launcher]: Starting browser PhantomJS
30 05 2018 18:18:58.293:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket UwfYAt7yHauyEGfNAAAA with id 26585183
Snackbar component
✗ renders the correct markup
undefined is not a function (evaluating 'vNodes.findIndex(function (node) { return vNode.elm === node.elm; })')
webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2887:48 <- index.js:145115:83
filter#[native code]
removeDuplicateNodes#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2887:0 <- index.js:145115:23
findVNodesBySelector#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2917:0 <- index.js:145145:30
findVnodes#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2934:0 <- index.js:145162:30
find#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2982:0 <- index.js:145210:27
find$$1#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:3272:0 <- index.js:145500:19
webpack:///test/unit/specs/snackbar.spec.js:38:32 <- index.js:142013:32
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.622 secs / 0.009 secs)
This is a problem with an old version of vue-test-utils. findIndex is not supported in IE, so we have removed findIndex from recent versions.
If you cannot update to the latest #vue/test-utils, you can add a findIndex polyfill before you run the tests—https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex#Polyfill.
I am currently testing one of my children components
I already tested successfully all vuex actions, mutations and getters
When I run this ChangeTitleComonent.spec.js , it passes with 100% coverage.. thanks for feedback
Do I need to write some additional tests when it's 100% covered ? or is this test bad written ?
PARENT
ShoppingListComponent.vue
<template>
<div>
<h2>{{ title }}</h2>
<add-item-component :id='id' #add="addItem"></add-item-component>
<items-component :items="items" :id="id"></items-component>
<div class="footer">
<hr />
<change-title-component :title="title" :id="id"></change-title-component>
</div>
</div>
</template>
<script>
import AddItemComponent from './AddItemComponent'
import ItemsComponent from './ItemsComponent'
import ChangeTitleComponent from './ChangeTitleComponent'
export default {
components: {
AddItemComponent,
ItemsComponent,
ChangeTitleComponent
},
props: ['id', 'title', 'items'],
methods: {
addItem (text) {
this.items.push({
text: text,
checked: false
})
}
}
}
</script>
<style scoped>
.footer {
font-size: 0.7em;
margin-top: 20vh;
}
</style>
CHILDREN
ChangeTitleComponent
<template>
<div>
<em>Change the title of your shopping list here</em>
<input :value="title" #input="onInput({ title: $event.target.value, id: id })"/>
</div>
</template>
<style scoped>
</style>
<script>
import { mapActions } from 'vuex'
export default {
props: ['title', 'id'],
methods: mapActions({ // dispatching actions in components
onInput: 'changeTitle'
})
}
</script>
UNIT TEST
ChangeTitleComponent.spec.js
import Vue from 'vue'
import ChangeTitleComponent from '#/components/ChangeTitleComponent'
import store from '#/vuex/store'
describe('ChangeTitleComponent.vue', () => {
describe('changeTitle', () => {
var component
beforeEach(() => {
var vm = new Vue({
template: '<change-title-component :title="title" :id="id" ref="changetitlecomponent">' +
'</change-title-component></div>',
components: {
ChangeTitleComponent
},
props: ['title', 'id'],
store
}).$mount()
component = vm.$refs.changetitlecomponent
})
it('should change the title', () => {
// check component label text
expect(component.$el.textContent).to.equal('Change the title of your shopping list here ')
// simulate input Enter event
const input = component.$el.querySelector('input')
input.value = 'My New Title'
const enterEvent = new window.Event('keypress', { which: 13 })
input.dispatchEvent(enterEvent)
component._watcher.run()
})
})
})
This is my test code
import { shallow } from 'enzyme';
import ApplicationStatus
from 'models/ApplicationStatus';
import ApplicationStatusLabel
from 'component/ApplicationStatus';
describe('<ApplicationStatusLabel />', () => {
it(`should render a label if a user have submitted an
application`, () => {
const status = new ApplicationStatus({ status: 'pending' });
const wrapper = shallow(
<ApplicationStatusLabel
planApplicationStatus={status}
/>
);
console.log(wrapper.text())
expect(wrapper.contains('received your application')).to.equal(true);
});
and there is my label react code
const PlanApplicationReceivedStatus = () => (
<div className={styles['plan-trial']}>
<div className={styles['plan-trial-headline']}>
{"We've received your application."}
</div>
<div className={styles['plan-trial-text']}>
{
`We'll get back to you via email within 24 hours.
`
}
</div>
</div>
);
const ApplicationStatusLabel = (
{ planApplicationStatus }
) => {
if (
planApplicationStatus &&
planApplicationStatus.status === 'pending'
) {
return PlanApplicationReceivedStatus();
}
return null;
};
However the test always reports failure
AssertionError: expected false to equal true
+ expected - actual
-false
+true
at Assertion.assertEqual (node_modules/chai/lib/chai/core/assertions.js:487:12)
at Assertion.ctx.(anonymous function) [as equal] (node_modules/chai/lib/chai/utils/addMethod.js:41:25)
at Context.<anonymous> (test/components/PlanApplicationStatus.spec.js:19:
I have put in a console.log statement to print out the content. I can see the output as such:
We've received your application.We'll get back to you via email within 24 hours.
So it seems to me the react component works as expected. So why the contains test failed?
I am working on an app which was created with the Vue loader's webpack template.
I included testing with Karma as an option when creating the project, so it was all set up and I haven't changed any of the config.
The app is a Github user lookup which currently consists of three components; App.vue, Stats.vue and UserForm.vue. The stats and form components are children of the containing app component.
Here is App.vue:
<template>
<div id="app">
<user-form
v-model="inputValue"
#go="submit"
:input-value="inputValue"
></user-form>
<stats
:username="username"
:avatar="avatar"
:fave-lang="faveLang"
:followers="followers"
></stats>
</div>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import _ from 'lodash'
import UserForm from './components/UserForm'
import Stats from './components/Stats'
Vue.use(VueAxios, axios)
export default {
name: 'app',
components: {
UserForm,
Stats
},
data () {
return {
inputValue: '',
username: '',
avatar: '',
followers: [],
faveLang: '',
urlBase: 'https://api.github.com/users'
}
},
methods: {
submit () {
if (this.inputValue) {
const api = `${this.urlBase}/${this.inputValue}`
this.fetchUser(api)
}
},
fetchUser (api) {
Vue.axios.get(api).then((response) => {
const { data } = response
this.inputValue = ''
this.username = data.login
this.avatar = data.avatar_url
this.fetchFollowers()
this.fetchFaveLang()
}).catch(error => {
console.warn('ERROR:', error)
})
},
fetchFollowers () {
Vue.axios.get(`${this.urlBase}/${this.username}/followers`).then(followersResponse => {
this.followers = followersResponse.data.map(follower => {
return follower.login
})
})
},
fetchFaveLang () {
Vue.axios.get(`${this.urlBase}/${this.username}/repos`).then(reposResponse => {
const langs = reposResponse.data.map(repo => {
return repo.language
})
// Get most commonly occurring string from array
const faveLang = _.chain(langs).countBy().toPairs().maxBy(_.last).head().value()
if (faveLang !== 'null') {
this.faveLang = faveLang
} else {
this.faveLang = ''
}
})
}
}
}
</script>
<style lang="stylus">
body
background-color goldenrod
</style>
Here is Stats.vue:
<template>
<div class="container">
<h1 class="username" v-if="username">{{username}}</h1>
<img v-if="avatar" :src="avatar" class="avatar">
<h2 v-if="faveLang">Favourite Language: {{faveLang}}</h2>
<h3 v-if="followers.length > 0">Followers ({{followers.length}}):</h3>
<ul v-if="followers.length > 0">
<li v-for="follower in followers">
{{follower}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'stats',
props: [
'username',
'avatar',
'faveLang',
'followers'
]
}
</script>
<style lang="stylus" scoped>
h1
font-size 44px
.avatar
height 200px
width 200px
border-radius 10%
.container
display flex
align-items center
flex-flow column
font-family Comic Sans MS
</style>
And here is UserForm.vue:
<template>
<form #submit.prevent="handleSubmit">
<input
class="input"
:value="inputValue"
#input="updateValue($event.target.value)"
type="text"
placeholder="Enter a GitHub username..."
>
<button class="button">Go!</button>
</form>
</template>
<script>
export default {
props: ['inputValue'],
name: 'user-form',
methods: {
updateValue (value) {
this.$emit('input', value)
},
handleSubmit () {
this.$emit('go')
}
}
}
</script>
<style lang="stylus" scoped>
input
width 320px
input,
button
font-size 25px
form
display flex
justify-content center
</style>
I wrote a trivial test for UserForm.vue which test's the outerHTML of the <button>:
import Vue from 'vue'
import UserForm from 'src/components/UserForm'
describe('UserForm.vue', () => {
it('should have a data-attribute in the button outerHTML', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(UserForm)
})
expect(vm.$el.querySelector('.button').outerHTML)
.to.include('data-v')
})
})
This works fine; the output when running npm run unit is:
UserForm.vue
✓ should have a data-attribute in the button outerHTML
However, when I tried to write a similarly simple test for Stats.vue based on the documentation, I ran into a problem.
Here is the test:
import Vue from 'vue'
import Stats from 'src/components/Stats'
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(Stats).$mount()
vm.username = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.querySelector('.username').textContent).toBe('foo')
done()
})
})
and here is the respective error when running npm run unit:
ERROR LOG: '[Vue warn]: Error when rendering root instance: '
✗ updates the rendered message when vm.message updates
undefined is not an object (evaluating '_vm.followers.length')
I have tried the following in an attempt to get the test working:
Change how the vm is created in the Stats test to be the same as the UserForm test - same error is returned
Test individual parts of the component, for example the textContent of a div in the component - same error is returned
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front? How can I get around this issue to be able to successfully test my component?
(Repo with all code: https://github.com/alanbuchanan/vue-github-lookup-2)
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front?
This piece of code is from the render function that Vue compiled your template into. _vm is a placeholder that gets inserted automatically into all Javascript expressions when vue-loader converts the template into a render function during build - it does that to provide access to the component.
When you do this in your template:
{{followers.length}}
The compiled result in the render function for this piece of code will be:
_vm.followers.length
Now, why does the error happen in the first place? Because you have defined a prop followers on your component, but don't provide any data for it - therefore, the prop's value is undefined
Solution: either you provide a default value for the prop:
// Stats.vue
props: {
followers: { default: () => [] }, // function required to return fresh object
// ... other props
}
Or you propvide acual values for the prop:
// in the test:
const vm = new Vue({
...Stats,
propsData: {
followers: [/* ... actual data*/]
}
}).$mount()