Testing Methods within Vue Components using Jasmine - vue.js

I have the following test which works great
it('does not render chapter div or error div', () => {
const payLoad = chapter;
const switcher = 'guild';
var vm = getComponent(payLoad, switcher).$mount();
expect(vm.$el.querySelector('#chapter-card')).toBeNull();
expect(vm.$el.querySelector('#error-card')).toBeNull();
});
To do this I wrote a helper method that mounts a component:
const getComponent = (prop1) => {
let vm = new Vue({
template: '<div><compd :payLoad="group" :index="index" "></compd ></div></div>',
components: {
compd,
},
data: {
payLoad: prop1,
},
})
return vm;
}
however, I have a method within my vue component compd. For simplicitys sake, lets call it
add(num,num){
return num+num;
}
I want to be able to write a test case similar to the following:
it('checks the add method works', () => {
expect(compd.add(1,2).toBe(3));
});
I cannot figure out how to do this. Has anyone any suggestions?
The documentation here:
https://v2.vuejs.org/v2/guide/unit-testing.html
Does not cover testing methods.

Source code from vue repo
As you can see the method gets called simply on the instance
const vm = new Vue({
data: {
a: 1
},
methods: {
plus () {
this.a++
}
}
})
vm.plus()
expect(vm.a).toBe(2)
You can also access the method via $options like in this case (vue source code)
const A = Vue.extend({
methods: {
a () {}
}
})
const vm = new A({
methods: {
b () {}
}
})
expect(typeof vm.$options.methods.a).toBe('function')
Update:
To test child components use $children to access the necessary child. Example
var childToTest = vm.$children.find((comp)=>comp.$options.name === 'accordion')` assuming name is set to `accordion`
After that you can
childToTest.plus();
vm.$nextTick(()=>{
expect(childToTest.someData).toBe(someValue)
done(); //call test done callback here
})
If you have a single child component and not a v-for put a ref on it
`
vm.$refs.mycomponent.myMethod()

Related

Using XState in Nuxt 3 with asynchronous functions

I am using XState as a state manager for a website I build in Nuxt 3.
Upon loading some states I am using some asynchronous functions outside of the state manager. This looks something like this:
import { createMachine, assign } from "xstate"
// async function
async function fetchData() {
const result = await otherThings()
return result
}
export const myMachine = createMachine({
id : 'machine',
initial: 'loading',
states: {
loading: {
invoke: {
src: async () =>
{
const result = await fetchData()
return new Promise((resolve, reject) => {
if(account != undefined){
resolve('account connected')
}else {
reject('no account connected')
}
})
},
onDone: [ target: 'otherState' ],
onError: [ target: 'loading' ]
}
}
// more stuff ...
}
})
I want to use this state machine over multiple components in Nuxt 3. So I declared it in the index page and then passed the state to the other components to work with it. Like this:
<template>
<OtherStuff :state="state" :send="send"/>
</template>
<script>
import { myMachine } from './states'
import { useMachine } from "#xstate/vue"
export default {
setup(){
const { state, send } = useMachine(myMachine)
return {state, send}
}
}
</script>
And this worked fine in the beginning. But now that I have added asynchronous functions I ran into the following problem. The states in the different components get out of sync. While they are progressing as intended in the index page (going from 'loading' to 'otherState') they just get stuck in 'loading' in the other component. And not in a loop, they simply do not progress.
How can I make sure that the states are synced in all my components?

How to set mock nuxt asyncData in jest

I am using Nuxt.js and want to test my page which uses asyncData with Jest. I have a factory function to set up my wrapper, but it basically returns a shallowMount.
Expected
When clicking a button I want the function to behave differently depending on the query parameter. When running the test I want to mock this by setting it directly when creating the wrapper (Similar to setting propsData). E.g. const wrapper = factory({ propsData: { myQueryParam: 'some-value' } });
Result
However trying to set propsData still returns undefined: console.log(wrapper.vm.myQueryParam); // undefined while I would expect it to be 'some-value'
Question
Is there a different approach on how I can test this function that relies on query parameters?
Because asyncData is called before Vue is initialised, it means shallowMount doesn't work right out of the box.
Example:
page:
<template>
<div>Your template.</div>
</template>
<script>
export default {
data() {
return {}
},
async asyncData({
params,
error,
$axios
}) {
await $axios.get("something")
}
}
</script>
test:
import { shallowMount } from "#vue/test-utils";
describe('NewsletterConfirm', () => {
const axiosGetMock = jest.fn()
const axiosPostMock = jest.fn()
var getInitialised = async function (thumbprint) {
if (thumbprint == undefined) throw "thumbprint not provided"
let NewsletterConfirm = require('./_thumbprint').default
if (!NewsletterConfirm.asyncData) {
return shallowMount(NewsletterConfirm);
}
let originalData = {}
if (NewsletterConfirm.data != null) {
originalData = NewsletterConfirm.data()
}
const asyncData = await NewsletterConfirm.asyncData({
params: {
thumbprint
},
error: jest.fn(),
$axios: {
get: axiosGetMock,
post: axiosPostMock
}
})
NewsletterConfirm.data = function () {
return {
...originalData,
...asyncData
}
}
return shallowMount(NewsletterConfirm)
}
it('calls axios', async () => {
let result = await getInitialised("thumbprint")
expect(axiosGetMock).toHaveBeenCalledTimes(1)
});
});
Credits to VladDubrovskis for his comment: in this nuxt issue

Vue - unit test watching a vuex getter property

I have a getter: defaultInternalAccountId and I am watching this property in my component. When it changes, I call a method: fetchAccount, which is also called when the component is mounted. I need to create a test that asserts that it was called twice and that the second time it was called with the correct accountId. Here's an attempt:
test('Dispatches fetchAccount when defaultInternalAccountId is updated', () => {
const accountId = '1234';
const accountId2 = '5678';
mockDefaultInternalAccountId.mockReturnValueOnce(accountId);
mockDefaultInternalAccountId.mockReturnValueOnce(accountId2);
const wrapper = shallowMount(AssetAllocationsSummaryContainer, {
localVue,
store,
});
expect(mockFetchAccount.mock.calls[0][1]).toEqual({ accountId }); // There are other args that mapActions applies
wrapper.vm.$nextTick();
expect(mockFetchAccount).toHaveBeenCalledTimes(2);
expect(mockFetchAccount.mock.calls[1][1]).toEqual({ accountId: accountId2 }); // There are other args that mapActions applies
});
The above is failing at expect(mockFetchAccount).toHaveBeenCalledTimes(2); as mockFetchAccount is only being called once. Here is the watcher:
watch: {
defaultInternalAccountId: {
immediate: true,
handler(newAccountId, oldAccountId) {
if (newAccountId && newAccountId !== oldAccountId) {
this.fetchAccount({ accountId: newAccountId });
}
},
},
},
defaultInternalAccountId is a mapped getter: ...mapGetters(['defaultInternalAccountId']), and fetchAccount is a mapped action: ...mapActions(['fetchAccount']),. All is working as expected in vivo. I have also attempted setting state directly, but the component does not pick up on that change: store.state.defaultInternalAccountId = accountId2
Getters are cached so I needed to implement the mock getter using reactive state instead of mockReturnValue:
test('Dispatches fetchAccount when defaultInternalAccountId is updated', () => {
const accountId = '1234';
const accountId2 = '5678';
const vm = new Vue({ data: { accountId } });
mockDefaultInternalAccountId.mockImplementation(() => vm.accountId);
shallowMount(AssetAllocationsSummaryContainer, {
localVue,
store,
});
expect(mockFetchAccount.mock.calls[0][1]).toEqual({ accountId });
vm.accountId = accountId2;
expect(mockFetchAccount).toHaveBeenCalledTimes(2);
expect(mockFetchAccount.mock.calls[1][1]).toEqual({ accountId: accountId2 });
});

how to mock this.$refs.form.validate in vue

I have a vue code where i do some actions based on this.$refs.form.validate
I wanted to write a test for it ...But not sure how can i mock this.$refs.form.validate? I have written only basic ones..can someone point me at right direction?I am using Vue+Jest
methods: {
sayHello () {
if (this.$refs.form.validate()) {
//code goes here
}
Is there a way to make it return false and true?
create a stub
const VueFormStub = {
render: () => {},
methods: {
validate: () => {}
}
}
then in your wrapper add it like this
const wrapper = shallowMount(VueFile, {
stubs: {
'v-form': VueFormStub
}
})
What is strange $refs don't seem to work inside mocks property when we mount a component in a test. But if we do like this it works
const wrapper = mount(SidePanel);
wrapper.vm.$refs.checkbox = [{ focused: false }, { focused: true }];
So when component does something like this it will be ok
console.log(this.$refs.checkbox[0].focused);
You need to use jest method jest.fn() to mock your function. Like so const foo = jest.fn(). And the you need to test, if this fn has been called.

Use tableTop.js to return an array that can be used in Vue Components

I am attempting to build an array of objects from a spreadsheet using tableTop.js that can be passed into other functions and vue components. I have been unsuccessful in returning anything I can actually use. I found this post that got me close to what I am after however what it is returning is an array of arrays of objects with two undefined array items beginning with [ob: Observer]
If I log out data in the getLibrary() function I can see the correct array how I need to receive it in my component.
If I don't push the data into the gData array in libraryData I receive undefined in vue from the function. I have attempted promises, normal functions etc. but nothing seems to work. Very appreciative of any help anyone can provide thanks.
Image 1 is what I am logging out in library data that I am trying to receive in vue.
Image 2 is what I am getting in vue
libraryData.js
// let gData = []
export default async function () {
let spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { return getLibraryData(data,m tabletop) },
simpleSheet: true
})
}
export function getLibraryData(data, tabletop) {
// gData.push(data);
///gData = data
log(data)
// I just want to return the data here to be used in vue
return data;
}
index.js
import Vue from 'vue'
import libraryData from './partials/libraryData.js'
// Too be added into a vue-lodaer?
new Vue({
el: '#vhsLibrary',
router,
template: '<vhsLibrary/>',
})
window.addEventListener('DOMContentLoaded', () => {
libraryData()
})
vue_component.vue
<script>
import { getLibraryData } from '../../js/partials/library_data';
export default {
data: () => {
return {
gData: null
}
},
mounted () {
this.gData = getLibraryData()
log('Get Library', getLibraryData())
}
}
</script>
There's a few issues here:
You use async, but you never await. In your case, we want to await the resolution or rejection of a Promise:
export default async function () {
return await new Promise((resolve, reject) => {
const spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { resolve({data, tabletop}) },
simpleSheet: true
})
})
}
There's no reason for the additional function because it has no gains. Let's look at Vue now.
First, your gData variable is initialized as null as opposed to []. Let's change that:
data () {
return {
gData: []
}
},
Next, let's update our mounted method. We can use the same async/await pattern here:
async mounted () {
const { data } = await getLibraryData()
this.gData = data
}
And now you can v-for="(row, index) in gData" to iterate it.
Here's a codepen for you, too