Testing Vuejs EventBus - javascript

I have been trying to figure this out for some time already, but I can not make it work! I found some examples on internet, but nothing solves it, every time I run my test I get:
Expected number of calls: 1
Received number of calls: 0
> 186 | expect(wrapper.vm.EventBus.$on).toHaveBeenCalledTimes(1);
The component looks like this:
import {EventBus} from 'eventbus'
export default{
data(){ return {}},
mounted(){
EventBus.$on('saveTerminal', function(){
this.someOtherFunction()
})
}
}
Event Bus file looks like this
import Vue from 'vue';
export const EventBus = new Vue();
The Test looks like this:
const GlobalPlugins = {
install(v) {
v.prototype.EventBus = new Vue();
},
};
//Vue.prototype.EventBus = new Vue(); <-- I tried this also, didn't do anything
// Mounting component
const $t = () => {}
const params = { localVue, store, router,
propsData: {
isEdit: false
},
data(){
return {
loading: false,
tabIndex: 1
};
},
mocks:{
$t,
EventBus: {
$on: jest.fn(),
$off: jest.fn(),
$emit: jest.fn()
}
},
}
const wrapper = shallowMount(MyComponent, params)
describe('My component', () => {
it('Event bus', () => {
wrapper.vm.EventBus.$emit('saveTerminal');
expect(wrapper.vm.EventBus.$on).toHaveBeenCalledTimes(1);
expect(wrapper.vm.EventBus.$on).toHaveBeenCalledWith('saveTerminal', expect.any(Function))
});
})

You can use jest.mock() to mock the EventBus module. Your test would require() the module to access the mock, and then verify its $on was called:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
jest.mock('#/utils/EventBus')
describe('MyComponent.vue', () => {
it(`listens to 'saveTerminal' upon mounting`, async () => {
const { EventBus } = require('#/utils/EventBus')
shallowMount(MyComponent)
expect(EventBus.$on).toHaveBeenCalledWith('saveTerminal', expect.any(Function))
})
})
demo

Related

Unable to emit events when wrapping a Vue 3 component into a custom element

When using the following code to wrap a Vue 3 component into a custom element, I noticed that Vue events were not received by the caller.
import { createApp, defineCustomElement, getCurrentInstance, h } from "vue"
export const defineVueCustomElement = (component: any, { plugins = [] } = {}) =>
defineCustomElement({
styles: component.styles,
props: component.props,
emits: component.emits,
setup(props, { emit }) {
const app = createApp();
plugins.forEach((plugin) => {
app.use(plugin);
});
const inst = getCurrentInstance();
Object.assign(inst.appContext, app._context);
Object.assign(inst.provides, app._context.provides);
return () =>
h(component, {
...props,
});
},
})
But when I wrote a simpler code, Vue events can be received by the client correctly. The drawback of the code is that it doesn't support Vue plugins:
import { defineCustomElement } from "vue"
export const defineVueCustomElement = (component: any) => {
defineCustomElement(component)
}
I am wondering why the first piece of code was not working correctly? How should I correct it? Thanks!

How can I modify the value of state in pinia in vue3 component test and affect the component?

Using vue-test-utils to test the component using pinia, I need to modify the value of the state stored in pinia, but I have tried many methods to no avail. The original component and store files are as follows.
// HelloWorld.vue
<template>
<h1>{{ title }}</h1>
</template>
<script>
import { useTestStore } from "#/stores/test";
import { mapState } from "pinia";
export default {
name: "HelloWorld",
computed: {
...mapState(useTestStore, ["title"]),
},
};
</script>
// #/stores/test.js
import { defineStore } from "pinia";
export const useTestStore = defineStore("test", {
state: () => {
return { title: "hhhhh" };
},
});
The following methods have been tried.
Import the store used within the component to the test code and make changes directly, but the changes cannot affect the component.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
import { useTestStore } from "#/stores/test";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia()],
},
});
const store = useTestStore();
store.title = "xxxxx";
console.log(wrapper.text()) //"hhhhh";
});
Using the initialState in an attempt to overwrite the contents of the original store, but again without any effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia({ initialState: { title: "xxxxx" } })],
},
});
console.log(wrapper.text()) //"hhhhh";
});
Modify the TestingPinia object passed to global.plugins in the test code, but again has no effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const pinia = createTestingPinia();
pinia.state.value.title = "xxxxx";
const wrapper = mount(HelloWorld, {
global: {
plugins: [pinia],
},
});
console.log(wrapper.text()) //"hhhhh";
});
Use global.mocks to mock the states used in the component, but this only works for the states passed in with setup() in the component, while the ones passed in with mapState() have no effect.
// test.spec.js
import { mount } from "#vue/test-utils";
import { createTestingPinia } from "#pinia/testing";
import HelloWorld from "#/components/HelloWorld.vue";
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: {
plugins: [createTestingPinia()],
mocks: { title: "xxxxx" },
},
});
console.log(wrapper.text()) //"hhhhh"
});
This has been resolved using jest.mock().
import { mount } from "#vue/test-utils";
import { createPinia } from "pinia";
import HelloWorld from "#/components/HelloWorld.vue";
jest.mock("#/stores/test", () => {
const { defineStore } = require("pinia");
const useTestStore = defineStore("test", { state: () => ({ title: "xxxxx" }) });
return { useTestStore };
});
test("pinia in component test", () => {
const wrapper = mount(HelloWorld, {
global: { plugins: [createPinia()] },
});
expect(wrapper.text()).toBe("xxxxx");
});
Thanks to Red Panda for this topic. I use "testing-library", and "vue-testing-library" instead of "vue-test-utils" and "jest", but the problem is the same - couldn't change pinia initial data of the store.
I finally found a solution for this issue without mocking the function.
When you $patch data, you just need to await for it. Somehow it helps. My code looks like this and it totally works:
Popup.test.js
import { render, screen } from '#testing-library/vue'
import { createTestingPinia } from '#pinia/testing'
import { popup } from '#/store1/popup/index'
import Popup from '../../components/Popup/index.vue'
describe('Popup component', () => {
test('displays popup with group component', async () => {
render(Popup, {
global: { plugins: [createTestingPinia()] }
})
const store = popup()
await store.$patch({ popupData: 'new name' })
screen.debug()
})
})
OR you can set initialState using this scheme:
import { render, screen } from '#testing-library/vue'
import { createTestingPinia } from '#pinia/testing'
import { popup } from '#/store1/popup/index'
import Popup from '../../components/Popup/index.vue'
test('displays popup with no inner component', async () => {
const { getByTestId } = render(Popup, {
global: {
plugins: [
createTestingPinia({
initialState: {
popup: {
popupData: 'new name'
}
}
})
]
}
})
const store = popup()
screen.debug()
})
Where popup in initialState - is the imported pinia store from #/store1/popup. You can specify any of them there the same way.
Popup.vue
<script>
import { defineAsyncComponent, markRaw } from 'vue'
import { mapState, mapActions } from 'pinia'
import { popup } from '#/store1/popup/index'
export default {
data () {
return {}
},
computed: {
...mapState(popup, ['popupData'])
},
....
I'm working on a project using Vue 3 with composition API styling.
Composition API is used for both components and defining my store.
Here is my store
player.js
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
export const usePlayerStore = defineStore('player',()=>{
const isMainBtnGameClicked = ref(false)
return { isMainBtnGameClicked }
})
MyComponent.vue
//import { usePlayerStore } from '...'
const playerStore = usePlayerStore()
playerStore.isMainBtnGameClicked = true
isMainBtnGameClicked from my store is updated properly.
You can also update variables from components by passing them by reference to the pinia store. It's working in my project.
For sake of saving future me many hours of trouble, there is a non-obvious thing in play here - the event loop. Vue reactivity relies on the event loop running to trigger the cascade of state changes.
When you mount/shallowMount/render a component with vue-test-utils, there is no event loop running automatically. You have to trigger it manually for the reactivity to fire, e.g.
await component.vm.$nextTick;
If you don't want to mess around with ticks, you have to mock the store state/getters/etc. (which the docs strongly lean toward, without explaining the necessity). Here OP mocked the whole store.
See also: Vue-test-utils: using $nextTick multiple times in a single test

'TypeError: Cannot read properties of undefined (reading 'getters')' occurs in vue-test-utils

I'm testing Vue project with vue-test-utils and jest.
...
export default Vue.extend({
components: { MemoInputAndButton, DomeMemoLine },
data (): {
memoLineList: Array<IMemoLine>
} {
return {
memoLineList: []
}
},
computed: {
...mapGetters('fraudTransactionState', {
fraudTransactionDetail: FraudTransactionGetters.FraudTransactionDetail
})
},
created: async function () {
await this.setMemoLineList()
},
methods: {
...mapActions('fraudTransactionState', {
postFraudTransactionMemo: FraudTransactionActions.PostFraudTransactionMemo
}),
async onMemoInputted (input: string): Promise<void> {
await this.postMemo(input)
},
...
MemoTimelineSection.vue
It uses vuex in modules which is namespaced: true!
const localVue = createLocalVue()
localVue.use(ElementUI)
localVue.use(Vuex)
describe('MemoTimelineSection', () => {
const div = document.createElement('div')
div.id = 'root'
document.body.appendChild(div)
let wrapper
let getter = {
'FRAUD_STATICS': () => { },
'FRAUD_TRANSACTIONS': () => { },
'FRAUD_TRANSACTION_DETAIL': () => { }
}
let store
beforeEach(() => {
...
const div = document.createElement('div')
div.id = 'root'
document.body.appendChild(div)
...
store = new Vuex.Store({
modules: {
fraudTransactionState: {
getter,
namespaced: true
}
}
})
MemoTimelineSection.test.js
and actual vuex store is
export const store: StoreOptions<RootState> = {
state: {
...
},
mutations: {
...
},
actions: {},
modules: {
transfersStore,
fraudTransactionState
},
getters: {}
}
export default new Vuex.Store<RootState>(store)
index.ts
When I run the test file,
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'getters')
33 | console.log('run')
34 |
> 35 | export default new Vuex.Store<RootState>(store)
| ^
36 |
occurs.
When I remove mapGetters and mapActions from Vue code, this error doesn't appear so I think it's related to mapGetters or mapActions or something related to Vuex...
The other code, which doesn't use vuex store in modules(only in roots) and is composed of vue-class-component, is running correctly even though it also tests vuex.
How can I solve it? Thx in advance.

TypeError: (0 , _testUtils.createLocalVue) is not a function

This is my code. Can some please help me figure out the error.I am using jest to test out my frontend which I have built using Vue.The line const localVue = createLocalVue(); is giving out the error TypeError: (0 , _testUtils.createLocalVue) is not a function
import { createLocalVue,shallowMount } from '#vue/test-utils'
import Vuex from 'vuex'
import getters from '../../src/store/module/auth/getters.js'
import TheHeader from '#/components/layout/TheHeader.vue'
// const store = new Vuex.Store({
// state: {
// user:null,
// token:'',
// expiresIn:null,
// isUserLoggedIn:false,
// isAdminLoggedIn:false,
// }
// })
describe('TheHeader', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store
let state
it('Checks whether the login is correctly displayed', () => {
const cmp = shallowMount(TheHeader, { store,localVue})
expect(cmp.name()).toMatch('TheHeader')
expect(cmp.vm.isLoggedIn()).toBe(false)
})
})
createLocalVue was removed in version 2 of #vue/test-utils, which explains why it's undefined in your example.
To install a Vue plugin (such as Vuex), use the global.plugins mounting option
To mock instance APIs (such as this.$store), use the global.mocks mounting option
import Vuex from 'vuex'
import { shallowMount } from '#vue/test-utils'
import TheHeader from '#/components/TheHeader.vue'
const store = /* Vuex store */
const cmp = shallowMount(TheHeader, {
global: {
plugins: [Vuex],
// OR:
mocks: {
$store: store,
}
}
})
import { mount } from '#vue/test-utils'
import { createApp } from 'vue'
import { createStore } from 'vuex'
import App from '#/views/Home'
creating a fake store
const store = createStore({
state() {
return {
count: 0,
user: {},
}
},
mutations: {
increment(state) {
state.count += 1
},
},
})
Creating the Component
const app = createApp(App)
app.use(store)
let wrapper
beforeEach(() => {
wrapper = mount(App, {
global: {
plugins: [store],
},
computed: { showAlert: () => false },
})
})
now you can do the test
test('Home', async () => {
expect(wrapper.vm.showAlert).toBe(false)
})

Unit testing a Vue plugin

I have a plugin that console.logs data.
logData.spec.js
import Vue from 'vue'
import { createLocalVue } from '#vue/test-utils'
import logData from './logData'
describe('logData plugin', () => {
const localVue = createLocalVue()
it('adds a $logData method to the Vue prototype', () => {
expect(Vue.prototype.$logData).toBeUndefined()
localVue.use(logData)
expect(typeof localVue.prototype.$logData).toBe('function')
})
it('console.logs data passed to it', () => {
const data = 'data to be logged'
const localVue = createLocalVue()
localVue.use(logData)
expect(localVue.prototype.$logData(data)).toBe('data to be logged')
})
})
logData.js
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
}
}
export default {
install: function (Vue) {
Vue.prototype.$logData = logData
}
}
The error I get is in my unit test is Expected: 'data to be logged", Received: undefined. Why is the second test being read as undefined?
It's expected behavior since console.log() returns undefined. To get desired result you should add this line of code to your lodData function:
return dataToLog
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
return dataToLog
}
}
NOTICE: Also you don't have localStorage in your test environment.

Categories

Resources