Mocking mixin in Vue Test libs - vue.js

I'm having a couple of issues with my Vue test libs. I am trying to test a mixin. It requires setting the route and mocking a function. Here is my code
MIXIN
export const CampaignNotifier = {
mounted () {
// Create tagname with dynamic currency parameter from banking app
let routeName = this.$route.name
let queryParamCurrency = (this.$route.query.currency) ? `- ${ this.$route.query.currency.toUpperCase() }` : '-'
this.campaignTagName = (BRAZE_TAG_MAPPING[routeName]) ? BRAZE_TAG_MAPPING[routeName].replace(/-/, queryParamCurrency) : null
if (this.campaignTagName) {
this.$appboy.logCustomEvent(this.campaignTagName)
}
},
}
TEST:
import { expect } from 'chai'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import VueRouter from 'vue-router'
import sinon from 'sinon'
import { CampaignNotifier } from '#/mixins/campaignNotifier'
let wrapper
function factory (routeName, currency) {
let localVue = createLocalVue()
localVue.use(VueRouter)
let routes = [
{
path: routeName,
name: routeName,
query: {
currency
}
}
]
let router = new VueRouter({
routes
})
let Component = {
render () { },
mixins: [CampaignNotifier],
localVue,
router,
mocks: {
$route: {
path: routeName,
query: {
currency
}
},
$appboy: {
logCustomEvent: () => {}
}
}
}
return shallowMount(Component)
}
describe('#/mixins/campaignNotifier', () => {
it('Campaign Notifier is not called if route not configured correctly', () => {
wrapper = factory('before-begin', 'EUR')
console.log('$route ***', wrapper.vm.$route)
sinon.spy(wrapper.vm.$appboy, 'logCustomEvent')
expect(wrapper.vm.$appboy.logCustomEvent).not.toHaveBeenCalled()
})
})
Issues I am encountering:
When I mock the $route and console.log it, it returns undefined. I tried mocking it and also using VueRouter. Neither worked.
I am trying to mock the global prototype / method $appboy.logCustomEvent I get:
[Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'logCustomEvent' of undefined"
Any help would be greatly appreciated. Thanks

You're incorrectly combining mounting options with the component definition itself, so your mocks aren't actually getting installed, leading to the errors you observed.
Mounting options should be passed as the second argument to shallowMount:
function factory() {
let Component = {
render() { },
mixins: [CampaignNotifier],
}
let mountingOptions = {
localVue,
router,
mocks: {
$route: {
path: routeName,
query: {
currency
}
},
$appboy: {
logCustomEvent: () => { }
}
}
}
return shallowMount(Component, mountingOptions)
}

Related

How can I access useRoute in Cypress Vue?

In my component there are conditionals and labels using route.meta.
import { useRoute } from 'vue-router';
const route = useRoute();
const name = route.meta?.name;
When running the test in Cypress I get an error
TypeError: Cannot read properties of undefined (reading 'meta')
I have attempted to use mocking the route however this doesn't resolve the issue
const mountTalentCommunityDetailsPage = (props: Record<string, unknown>) => {
const mockRoute = {
params: {
meta: {},
},
}
return mount(
{
components: {
MyComponent,
},
template: `<MyComponent/>`,
},
{
global: {
mocks: {
$route: mockRoute,
},
provide: {
useRoute,
},
},
}
)
}
Alternatively I could update name to const name = route.meta?.name; , however I don't think it's great to update this to fit the test
Thank you in advance!

Vue testing with jest: $route is always undefined

I've tried a lot of things but $route seems to always be undefined and I have this error on shallowMount: TypeError: Cannot read property 'params' of undefined.
In my component, I want to check the params of the $route but it is always undefined. I've looked the documentation and mocked the $route to set it but it seems like the $route is not mocked. I've also tried using localVue.use(VueRouter), thinking that it would set $route but it didn't. Does anyone have an idea why the following doesn't work in both cases?
my-component.ts
#Component
export default class MyComponent extends Vue {
get organisationId() {
return this.$route.params.organisationId;
}
}
I've tried the 2 following tests with the solutions I've talked about:
my-component.spec.ts
import { createLocalVue, shallowMount } from '#vue/test-utils';
import MyComponent from '#/components/MyComponent.vue';
import router from '#/router';
const localVue = createLocalVue();
describe('MyComponent', () => {
let cmp: any;
beforeEach(() => {
cmp = shallowMount(MyComponent, {
localVue,
router,
mocks: {
$route: {
params: {
id: 'id'
}
}
}
});
});
it('Should render a Vue instance', () => {
expect.assertions(1);
expect(cmp.isVueInstance()).toBeTruthy();
});
});
my-component.spec.ts
import { createLocalVue, shallowMount } from '#vue/test-utils';
import MyComponent from '#/components/MyComponent.vue';
const localVue = createLocalVue();
describe('MyComponent', () => {
let cmp: any;
localVue.use(VueRouter);
const router = new VueRouter();
beforeEach(() => {
cmp = shallowMount(MyComponent, {
localVue,
router
});
});
it('Should render a Vue instance', () => {
expect.assertions(1);
expect(cmp.isVueInstance()).toBeTruthy();
});
});
You were mocking $route right in #1 test. You can access $route property of test component via wrapper.vm.$route (or cmp.vm.$route in your case). And There is example:
import { shallowMount, Wrapper } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
let wrapper: Wrapper<MyComponent>
describe('MyComponent', () => {
beforeEach(() => {
wrapper = shallowMount(MyComponent, {
mocks: {
$route: {
params: {
id: 'id'
}
}
}
})
})
it('$route has passed params', () => {
expect(wrapper.vm.$route.params.id).toBe('id')
})
})

Mock of store action of vuex does not mocked

I have small vue component that on created hook dispatch some action
#Component
export default class SomeComponent extends Vue {
created() {
store.dispatch('module/myAction', { root: true });
}
}
and I wrote next test
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueRouter);
const localRouter = new VueRouter();
describe('SomeComponent.vue Test', () => {
let store: any;
beforeEach(() => {
store = new Vuex.Store({
modules: {
module: {
namespaced: true,
actions: {
myAction: jest.fn()
}
}
}
});
});
it('is component created', () => {
const wrapper = shallowMount(SomeComponent, {
localVue,
store,
propsData: {}
});
expect(wrapper.isVueInstance()).toBeTruthy();
});
});
but for some reason the "real" code are executed and I got a warning
isVueInstance() is deprecated. In your test you should mock $store object and it's dispatch function. I fixed typo in created(), here's my version of SomeComponent and working test, hope that would help.
#Component
export default class SomeComponent extends Vue {
created () {
this.$store.dispatch('module/myAction', { root: true })
}
}
import { shallowMount, Wrapper } from '#vue/test-utils'
import SomeComponent from '#/components/SomeComponent/SomeComponent.vue'
let wrapper: Wrapper<SomeComponent & { [key: string]: any }>
describe('SomeComponent.vue Test', () => {
beforeEach(() => {
wrapper = shallowMount(SomeComponent, {
mocks: {
$store: {
dispatch: jest.fn()
}
}
})
})
it('is component created', () => {
expect(wrapper.vm.$store.dispatch).toBeCalled()
expect(wrapper.vm.$store.dispatch).toBeCalledWith('module/myAction', { root: true })
})
})
Also keep in mind that when you test SomeComponent or any other component you should not test store functionality, you should just test, that certain actions/mutations were called with certain arguments. The store should be tested separately. Therefore you don't need to create real Vuex Store when you test components, you just need to mock $store object.

How to call method in setup of vuejs3 app?

In vuejs3 app I retrieve data from db with axios in method, like :
<script>
import appMixin from '#/appMixin'
import app from './../../App.vue' // eslint-disable-line
import axios from 'axios'
const emitter = mitt()
export default {
name: 'adminCategoriesList',
mixins: [appMixin],
data: function () {
return {
categoriesPerPage: 20,
currentPage: 1,
categoriesTotalCount: 0,
categories: []
}
},
components: {
},
setup () {
const adminCategoriesListInit = async () => {
this.loadCategories() // ERROR HERE
}
function onSubmit (credentials) {
alert(JSON.stringify(credentials, null, 2))
console.log('this::')
console.log(this)
console.log('app::')
}
onMounted(adminCategoriesListInit)
return {
// schema,
onSubmit
}
}, // setup
methods: {
loadCategories() {
...
}
and I got error in browser's console :
Cannot read property 'loadCategories' of undefined
If to remove “this.” in loadCategories call
I got error :
'loadCategories' is not defined
I need to make loadCategories as method, as I need to cal;l it from different places.
Which way is correct ?
Thanks!
You could use composition and options api in the same component but for different properties and methods, in your case the data properties could be defined inside setup hook using ref or reactive, the methods could be defined as plain js functions :
import {ref} from 'vue'
export default {
name: 'adminCategoriesList',
mixins: [appMixin],
components: {
},
setup () {
const categoriesPerPage= ref(20);
const currentPage=ref(1);
const categoriesTotalCount=ref(0),
const categories=ref[])
const adminCategoriesListInit = async () => {
loadCategories()
}
function onSubmit (credentials) {
alert(JSON.stringify(credentials, null, 2))
}
functions loadCategories(){
...
}
onMounted(adminCategoriesListInit)
return {
// schema,
onSubmit,
categoriesPerPage,
currentPage,
categoriesTotalCount,
categories
}
},
the properties defined by ref could be used/mutated by property.value and used in template like {{property}}

How to test namespaced mapAction in Vue

I have a namespaced Vuex store in my Vue 2.6 app one module of which is like this:
//books.js
export default {
state: {
books: null
},
mutations: {
SET_BOOKS(state, books) {
state.books = books;
},
},
actions: {
setBooks: async function ({ commit }) {
//api call
commit('SET_BOOKS', books);
}
},
namespaced: true
};
I want to test a component that calls the setBooks action. I am using mapActions to access the actions. The relevant code is:
//Books.vue
methods: {
...mapActions("books", ["setBooks"]),
}
},
created: async function() {
await this.setBooks();
}
The problem is that my test can't find the action:
import { shallowMount } from '#vue/test-utils';
import Books from '#/views/Books';
import Vuex from 'vuex';
import flushPromises from 'flush-promises';
import { createLocalVue } from '#vue/test-utils'
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Books', () => {
let actions;
let store;
let wrapper;
beforeEach(() => {
store = new Vuex.Store({
state: {
books: {
books: null
}
},
actions: {
setBooks: jest.fn()
}
});
wrapper = shallowMount(Books, { store, localVue })
});
it("renders the books", async () => {
await flushPromises();
expect(actions.setBooks).toHaveBeenCalled();
});
});
I get an error [vuex] module namespace not found in mapActions(): books/
if I try to namespace the actions code in the my test to:
actions: {
books: {
setBooks: jest.fn()
}
}
I get TypeError: Cannot read property 'setBooks' of undefined
Please help, thank you!
The docs for vue-test-utils include an example of testing with modules.
Change your beforeEach:
beforeEach(() => {
actions = { setBooks: jest.fn() }
store = new Vuex.Store({
modules: {
books: {
state: { books: null },
actions
}
}
})
...
})
Your test calls actions.setBooks, but in your original code actions was simply declared, but not used in the creation of your store.