Vue Test Utils mount in multiple tests - vue.js

I am testing my Vue App using Vue Test Utils and Jest. Below is my dashboard component.
<template>
<div class="dashboard-v2">
<div class="component-container">
<component :loading="loading" :key="identifier" :is="currentTab" />
</div>
<SnackBar
v-on:snackBarHide="displaySnackBar = false"
:text="snackBarText"
:show="displaySnackBar"
:type="snackBarType"
/>
</div>
</template>
<script>
import { mapState } from "vuex";
import "#/shared/chart-kick";
import EventBus from "#/shared/event-bus";
import Tabs from "./helpers/Tabs";
import Summary from "./Summary/Index";
import { filters } from "../helpers/filters-details";
import SnackBar from "#/shared/components/SnackBar.vue";
export default {
components: {
Tabs,
Summary,
SnackBar
},
data() {
return {
identifier: +new Date(),
loading: false,
filtersLoading: false,
displaySnackBar: false,
snackBarText: "",
snackBarType: ""
};
},
mounted() {
if (!this.projects.length) this.fetchFilterData();
EventBus.$on("CLEAR_ALL", () => {
this.identifier = +new Date();
this.$store.commit(`dashboardV2/UPDATE_FILTER_STATE`, {});
});
EventBus.$on("filterChange", () => {
this.getExecData();
});
},
computed: {
...mapState("dashboardV2", [
"projects",
"currentTab",
"selectedFilters",
"timeFilter"
])
},
methods: {
fetchFilterData() {
this.filtersLoading = true;
this.$store
.dispatch("dashboardV2/GET_EXEC_FILTER_DATA")
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => {
this.filtersLoading = false;
});
this.getExecData();
},
getExecData() {
this.loading = true;
let params = {
time_bucket: this.timeFilter,
time_zone_offset: new Date().getTimezoneOffset()
};
filters.map(e => {
params[e.query] = this.selectedFilters[e.value]
? this.selectedFilters[e.value].id
: null;
});
this.$store
.dispatch("dashboardV2/GET_EXEC_DATA", params)
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => (this.loading = false));
}
}
};
</script>
<style lang="scss" scoped>
#import "#/styles/dashboard.scss";
</style>
Then this is my test file
import Main from "../Main.vue";
import mergeWith from "lodash.mergewith";
import { customizer, createWrapper } from "#/shared/test-helper";
import Vuex from "vuex";
import EventBus from "#/shared/event-bus";
let GET_EXEC_DATA = jest.fn(() => Promise.resolve());
let GET_EXEC_FILTER_DATA = jest.fn(() => Promise.resolve());
export const createStore = (overrides) => {
let storeOptions = {
modules: {
dashboardV2: {
namespaced: true,
state: {
projects: [],
currentTab: "",
selectedFilters: {},
timeFilter: "",
},
actions: {
GET_EXEC_DATA,
GET_EXEC_FILTER_DATA,
},
},
},
};
return new Vuex.Store(mergeWith(storeOptions, overrides, customizer));
};
describe("Loads Main Dashboard", () => {
it("should fetch chart data and filter data", () => {
createWrapper({}, Main, createStore());
expect.assertions(2);
expect(GET_EXEC_DATA).toBeCalled();
expect(GET_EXEC_FILTER_DATA).toBeCalled();
});
it("should call fetch chart data when filter changed", () => {
createWrapper({}, Main, createStore());
EventBus.$emit("filterChange");
expect.assertions(1);
expect(GET_EXEC_DATA).toBeCalledTimes(2);
});
});
My first test is running successfully but my second test is failing because GET_EXEC_DATA is being called 4 times instead of 2 times. Is it because it's being called once in the first test. Then, How do I avoid this?

Actually, I was able to solve this by clearing the mock functions
afterEach(() => {
jest.clearAllMocks();
});

Related

My data variable not reflect to components props in vue

I have this code
<template>
<AppLayout :user="user">
<router-view :user="user" />
</AppLayout>
</template>
<script setup>
import LoginService from '#/services/LoginService';
import { inject, onMounted } from 'vue';
let user = null;
const $cookies = inject('$cookies');
async function getFuncionario() {
const publicToken = $cookies.get('PublicToken');
console.log(publicToken);
if (publicToken) {
await LoginService.getFuncionarioForMenu(publicToken)
.then((res) => {
user = res.data;
})
.catch((error) => {
console.log(error);
// redirect pagina de erro
});
console.log(user);
}
}
onMounted(() => {
getFuncionario();
});
</script>
I passed user variable like a props for my components
This "user" variable isn't updated after set my data to API:
user = res.data;
This variable does not reflect in my component
My component
<script>
import { markRaw } from 'vue';
const emptyLayout = 'EmptyLayout';
export default {
name: 'AppLayout',
data: () => ({
layout: emptyLayout,
}),
props: {
user: null,
},
watch: {
$route: {
immediate: true,
async handler(route) {
try {
const component = await import(`#/layouts/${route.meta.layout}.vue`);
this.layout = markRaw(component?.default || emptyLayout);
} catch (e) {
this.layout = emptyLayout;
}
},
},
},
};
</script>
Any ideas
I found the answer
I change my variable to
const user = ref(null);
this ref make a reflect in lifecycle
and I set the variable like this
user.value = res.data;

Vue.js/nuxt.js - test a dynamically added method inside a component

I'm trying to make a test for dynamically created methods in one of my components the code goes like this.
<template>
<div id="app">
<div #click="executeDynamic('myCustomFunction')">Click me!</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
// These contain all dynamic user functions
userFuncs: {}
}
},
created () {
window.setTimeout(() => {
this.$set(this.userFuncs, 'myCustomFunction', () => {
console.log('whoohoo, it was added dynamically')
})
}, 2000)
},
methods: {
executeDynamic (name) {
if (this.userFuncs[name]) {
this.userFuncs[name]()
} else {
console.warn(`${name} was not yet defined!`)
}
}
}
};
</script>
test file
import WorkDateTime from "#/components/WorkDateTime.vue"
import Vue from "vue"
describe("WorkDateTime.vue", () => {
it("allowedDatesFrom: today -> NG", () => {
const that = {
$set: Vue.set
}
expect(WorkDateTime.data.userFuncs['myCustomFunction']).toBeTruthy()
})
}
code pen
https://codesandbox.io/s/vue-template-forked-ec7tg?file=/src/App.vue:0-662
Try something like that:
import { shallowMount } from '#vue/test-utils';
import WorkDateTime from '#/components/WorkDateTime.vue';
describe('WorkDateTime.vue', () => {
it('userFuncs empty', () => {
let wrapper = shallowMount(WorkDateTime);
expect(wrapper.vm.userFuncs).toStrictEqual({});
});
it('userFuncs filled', async () => {
let wrapper = shallowMount(WorkDateTime);
let wait3Seconds = () => new Promise(resolve => setTimeout(() => resolve(), 3000));
await wait3Seconds();
expect(wrapper.vm.userFuncs['myCustomFunction']).toBeInstanceOf(Function);
});
});

Testing a vue app with nuxt, vuex and jest gettig not supported error

I want to Unit test my component which mutates an object in a store module when a button is clicked.
I followed the article by brandon aaskov on how to unit test nuxt plus vuex, but I'm not able to reference the nuxt store Object.
I always get an output like this:
Error: Not supported
at Object.<anonymous> (...\tests\unit\plannerObjectSelector.spec.js:67:5)
> (the line NuxtStore = await import(storePath);)
at Object.asyncJestLifecycle (...\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:53:37)
at ...\node_modules\jest-jasmine2\build\queueRunner.js:43:12
at new Promise (<anonymous>)
at mapper (...\node_modules\jest-jasmine2\build\queueRunner.js:26:19)
at ...\node_modules\jest-jasmine2\build\queueRunner.js:73:41
This is the Component i want to test:
<template>
<v-container>
<v-row v-for="object in plannerObjects" :key="object.id">
<v-btn
v-if="object.id == activeObjectId"
color="primary"
class="button"
block
large
tile
#click="objectSelected(object)"
>
{{ object.name }}
</v-btn>
<v-btn
v-else
color="primary"
block
large
text
#click="objectSelected(object)"
:ref="'unSelectedBtn-' + object.id"
>
{{ object.name }}
</v-btn>
</v-row>
</v-container>
</template>
<script>
export default {
name: "spaceSelector",
props: {
plannerObjects: {
type: Array,
required: true
}
},
data: () => ({
activeObjectId: -1
}),
methods: {
objectSelected(object) {
console.log("Sroe object", object);
this.$store.commit("planner/setActivePlannerObject", object);
this.activeObjectId = object.id;
console.log(this.$store.getters["planner/activePlannerObject"]);
}
}
};
</script>
<style scoped>
.button {
width: 190px;
}
</style>
```
this is my jest.config.js file:
module.exports = {
preset: "#vue/cli-plugin-unit-jest/presets/no-babel",
setupFiles: ["<rootDir>/tests/unit/index.js"],
globalSetup: "<rootDir>/jest.setup.js"
};```
jest.setup.js:
import { Nuxt, Builder } from "nuxt";
import nuxtConfig from "./nuxt.config";
// these boolean switches turn off the build for all but the store
const resetConfig = {
loading: false,
loadingIndicator: false,
fetch: {
client: false,
server: false
},
features: {
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false
},
build: {
indicator: false,
terser: false
}
};
// we take our nuxt config, lay the resets on top of it,
// and lastly we apply the non-boolean overrides
const config = Object.assign({}, nuxtConfig, resetConfig, {
mode: "spa",
srcDir: nuxtConfig.srcDir,
ignore: ["**/components/**/*", "**/layouts/**/*", "**/pages/**/*"]
});
const buildNuxt = async () => {
const nuxt = new Nuxt(config);
await new Builder(nuxt).build();
return nuxt;
};
module.exports = async () => {
const nuxt = await buildNuxt();
// we surface this path as an env var now
// so we can import the store dynamically later on
process.env.buildDir = nuxt.options.buildDir;
};
unit/index.js
import Vue from "vue";
import Vuetify from "vuetify";
Vue.config.productionTip = false;
Vue.use(Vuetify);
and finally my test class:
import { shallowMount } from "#vue/test-utils";
import plannerObjectSelector from "../../components/core/bars/planner/plannerObjectSelector";
import { __createMocks as createStoreMocks } from "../../store";
import _ from "lodash";
import { createLocalVue } from "#vue/test-utils";
import Vuex from "vuex";
import Vuetify from "vuetify";
var plannerObjects = [
{
id:0
}
];
const factory = () => {
return shallowMount(plannerObjectSelector, {
propsData: {
plannerObjects: plannerObjects
}
});
};
describe("plannerObjectSelector.vue", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuetify);
// to use Store
let NuxtStore;
let store;
beforeAll(async () => {
// note the store will mutate across tests
const storePath = `${process.env.buildDir}/store/index.js`;
NuxtStore = await import(storePath);
});
beforeEach(async () => {
store = await NuxtStore.createStore();
});
it("renders", () => {
const wrapper = factory();
expect(wrapper.exists()).toBe(true);
});
it("buttonClickedStoresObjectInStore", () => {
const wrapper = factory();
var btnref = "unSelectedBtn-0";
const btn = wrapper.find({ ref: btnref });
btn.trigger("click");
// look whats in our Store
let plannerObject = store.getters["planner/activePlannerObject"];
console.log(plannerObject);
expect(plannerObject).toBe(plannerObjects[0]);
});
test("mounts properly", () => {
const wrapper = factory();
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("renders properly", () => {
const wrapper = factory();
expect(wrapper.html()).toMatchSnapshot();
});
});
And this is my folder structure:
I would be thankful for any advice.
It turns out pretty simple, just test the function directly, even though I had banged my head for some hours by going through the documents just like you.
// ~/store/getters.ts
import { GetterTree } from 'vuex'
import { RootState } from '~/store/state'
export default {
[Getters.KEY.IS_SIGN_IN]: (state: RootState): boolean => {
return Boolean(state.currentUser)
}
} as GetterTree<RootState, RootState>
// ~/store/__tests__/getters.test.js (do not use .ts)
import state from '~/store/state'
import getters, { Getters } from '~/store/getters'
describe('getters', () => {
it(Getters.KEY.IS_SIGN_IN, () => {
// currentUser: undefined
expect(getters[Getters.KEY.IS_SIGN_IN](state)).toBe(false)
// SignIn: true
state.currentUser = { ...$mockData.currentUser }
expect(getters[Getters.KEY.IS_SIGN_IN](state)).toBe(true)
})

Unable to test a lodash debounced vue method

I've been trying to test a confirmation button using jest and vue-test-utils. I'm using a debounced method to handle accidental double-clicking.
I've tried using jest.useFakeTimers() and jest.runOnlyPendingTimers(), but I'm still not seeing my button text changing in my tests. I've created an isolated test file below - any advice on what I'm doing wrong here would be greatly appreciated!
import { shallowMount, createLocalVue } from '#vue/test-utils'
import _ from 'lodash'
jest.useFakeTimers()
const myComponent = {
data(){
return {
confirming: false
}
},
computed: {
state(){
return this.confirming ? 'are you sure?' : 'default'
}
},
template: `<div #click="changeState">{{ state }}</div>`,
methods: {
changeState(){
this.doConfirm()
},
doConfirm: _.debounce(function(){
if(!this.confirming){
this.confirming = true
}else{
this.confirming = false
}
}, 500)
}
}
describe('Testing debounce methods', () => {
let $wrapper
beforeEach(() => {
$wrapper = shallowMount(myComponent)
})
test('Check default state', () => {
expect($wrapper.text()).toContain('default')
})
test('Check state changes with click', () => {
$wrapper.trigger('click')
jest.runOnlyPendingTimers()
expect($wrapper.text()).toContain('are you sure?')
})
})

Pre-fetch data using vuex and vue-resource

I'm building an app following this structure: http://vuex.vuejs.org/en/structure.html
My components/App.vue like this:
<template>
<div id="app">
<course :courses="courses"></course>
</div>
</template>
<script>
import Course from './course.vue'
import { addCourses } from '../vuex/actions'
export default {
vuex: {
getters: {
courses: state => state.courses,
},
actions: {
addCourses,
}
},
ready() {
this.addCourses(this.fetchCourses())
},
components: { Course },
methods: {
fetchCourses() {
// what do I have to do here
}
}
}
</script>
How can I fetch the data and set it to the state.courses ?
Thanks
I've just figured it out:
in /components/App.vue ready function, I just call:
ready() {
this.addCourses()
},
in vuex/actions.js:
import Vue from 'vue'
export const addCourses = ({ dispatch }) => {
Vue.http.get('/api/v1/courses')
.then(response => {
let courses = response.json()
courses.map(course => {
course.checked = false
return course
})
dispatch('ADD_COURSES', courses)
})
}
and in vuex/store.js:
const mutations = {
ADD_COURSES (state, courses) {
state.courses = courses
}
}