vue.js Test unit with test-utils not passing - unit-testing

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

Vue.js unit test child emit calls parent function

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 :)

wrapper.find() is throwing error in unit testing

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.

Vue.js Vuex Unit test on user input, can I consider my test OK when coverage is 100%

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

enzyme shallow's contains test comes negative even thought the expected text are generated

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?

Vue component testing using Karma: 'undefined is not an object'

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