vue.js test-utils Why my onSubmit function mock is not called? - vue.js

I am trying to test the submit form event using the following spec file :
ContactForm.spec.js
import Vue from "vue";
import Vuex from "vuex";
import { storeMock } from "./mocks.js";
import VeeValidate from "vee-validate";
import i18n from "#/locales";
import Vuetify from "vuetify";
import { mount, shallowMount } from "#vue/test-utils";
import ContactForm from "#/components/Home/ContactForm.vue";
Vue.use(Vuex);
Vue.use(VeeValidate, { errorBagName: "errors" });
Vue.use(Vuetify);
describe("ContactForm.vue", () => {
let wrapper;
let store = new Vuex.Store(storeMock);
const v = new VeeValidate.Validator();
beforeEach(() => {
const el = document.createElement("div");
el.setAttribute("data-app", true);
document.body.appendChild(el);
});
it("submit valid form when click submit", async () => {
// given
const getters = {
language: () => {
return "fr";
},
"authentication/loading": () => {
return false;
}
},
store = new Vuex.Store({ getters });
// const sendMessageMock = jest.fn( () => Promise.resolve() );
const sendMessageMock = jest.fn();
const options = {
sync: false,
store,
provide: () => ({
$validator: v
}),
i18n,
mocks : {
sendMessage: sendMessageMock
}
};
// when
wrapper = shallowMount(ContactForm, options);
const contactForm = wrapper.find('#contactForm');
const submitBtn= wrapper.find('#btnSend');
// console.log(contactForm.html());
// when
contactForm.trigger('submit.prevent');
// then
expect(sendMessageMock.called).toBe(true);
});
});
But this test does not pass ...
console.log
✕ submit valid form when click submit (157ms)
● ContactForm.vue › submit valid form when click submit
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: undefined
Difference:
Comparing two different types of values. Expected boolean but received undefined.
77 | contactForm.trigger('submit.prevent');
78 | // then
> 79 | expect(sendMessageMock.called).toBe(true);
| ^
80 | });
81 | });
82 |
at Object.toBe (tests/unit/ContactForm.spec.js:79:36)
at tryCatch (node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:114:21)
at step (node_modules/#babel/runtime/helpers/builtin/asyncToGenerator.js:10:30)
at _next (node_modules/#babel/runtime/helpers/builtin/asyncToGenerator.js:25:9)
at node_modules/#babel/runtime/helpers/builtin/asyncToGenerator.js:32:7
Here is the component vue to be tested
ContactForm.vue
<template>
<form id="contactForm" onSubmit="sendMessage">
<input v-model="language" type='hidden' name='locale'>
<v-layout row wrap align-center>
.../...
</v-layout>
<v-text-field
.../...
</v-text-field>
<v-textarea .../... ></v-textarea>
<v-btn round #click="clear">.../...</v-btn>
<v-btn id="btnSend" round large color="primary" type="submit">Submit</v-btn>
</form>
</template>
<script>
import { mapGetters } from "vuex";
.../...
import router from "#/router";
export default {
name: "contactForm",
data() {
return {
.../...
};
},
computed: {
...mapGetters(["language"]),
...mapGetters("authentication", ["loading"]),
honorificPrefix: function() {
.../...
}
},
watch: {
language(newLanguage) {
.../...
}
},
methods: {
setPrefix: function(value) {
.../...
},
sendMessage: function() {
.../...
},
clear: function() {
.../...
}
},
mounted() {
.../...
}
};
</script>
Feedback welcome ...

SOLVED ...
change the form tag
remove the mocks: block in shallowMount() and use setMethods() to replace the sendMessage() with the sendMessageMock()
it("submit valid form when click submit", async () => {
// given
const getters = {
language: () => {
return "fr";
},
"authentication/loading": () => {
return false;
}
},
store = new Vuex.Store({ getters });
const sendMessageMock = jest.fn();
const options = {
sync: false,
store,
provide: () => ({
$validator: v
}),
i18n
};
// when
wrapper = shallowMount(ContactForm, options);
wrapper.setMethods({ sendMessage: sendMessageMock });
const contactForm = wrapper.find('#contactForm');
// when
contactForm.trigger('submit.prevent');
// then
expect(sendMessageMock).toBeCalled();
});

Related

TypeError: Cannot read property 'type' of null - testing vue component with async functions

I am testing a component ComponentA.spec.js but I am getting TypeError: Cannot read property 'type' of null. It works if I get rid of the await keyword in the getData() function in the ComponentA. I am mocking the getData api call in my test but still it doesn't work.
This is the full stack
TypeError: C:[Privacy]\unknown: Cannot read property 'type' of null
at assert (node_modules/#babel/types/lib/asserts/generated/index.js:284:112)
at Object.assertIdentifier (node_modules/#babel/types/lib/asserts/generated/index.js:373:3)
at new CatchEntry (node_modules/regenerator-transform/lib/leap.js:93:5)
at Emitter.Ep.explodeStatement (node_modules/regenerator-transform/lib/emit.js:535:36)
at node_modules/regenerator-transform/lib/emit.js:323:12
at Array.forEach (<anonymous>)
at Emitter.Ep.explodeStatement (node_modules/regenerator-transform/lib/emit.js:322:22)
at Emitter.Ep.explode (node_modules/regenerator-transform/lib/emit.js:280:40)
This is Component A that i am trying to create tests for
<template>
<div class="d-flex flex-row">
<component-b />
<component-c />
</div>
</template>
<script>
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';
import { getData } from 'apis';
export default {
name: 'component-a',
components: {
ComponentB,
ComponentC,
},
async created() {
await this.getData();
},
methods: {
// This function is the culprit
async getData() {
try {
const response = await getData();
} catch {
//
}
},
},
};
</script>
This is my ComponentA.spec.js file
import Vuetify from 'vuetify';
import ComponentA from 'components/ComponentA';
import { createLocalVue, shallowMount, mount } from '#vue/test-utils';
jest.mock('shared/apis', () => {
const data = require('../fixedData/data.json');
return {
getData: jest.fn().mockResolvedValue(data),
};
});
const localVue = createLocalVue();
let vuetify;
function createShallowWrapper(options = {}) {
return shallowMount(ComponentA, {
localVue,
vuetify,
...options,
});
}
beforeEach(() => {
vuetify = new Vuetify();
});
describe('ComponentA', () => {
describe('component creation', () => {
test('testing', () => {
const wrapper = createShallowWrapper();
expect(wrapper).toMatchSnapshot();
});
});
});
Adding exception variable (e) to my catch in the getData function in ComponentA fixed it.

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;

Jest : TypeError: Cannot read property 'variable' of undefined

I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
List.spec.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import { createLocalVue, shallowMount } from '#vue/test-utils';
import List from '#/components/home/list.vue';
Vue.use(Vuetify);
describe('List.vue', () => {
const localVue = createLocalVue();
let vuetify;
const $t = () => {};
const localePath = () => {};
beforeEach(() => {
vuetify = new Vuetify();
localVue.use(vuetify);
});
const mockOrder = [
{
coardshare: {
cs_id: 123,
},
},
{
talkboard: {
cs_id: 123,
},
},
];
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data() {
return {
data: mockOrder,
};
},
});
expect(wrapper.html()).toMatchSnapshot();
const title = wrapper.find('.v-card__title > span');
expect(title.text()).toBe('Foobar');
});
});
List.vue
<template>
...
<div v-for="item in state.data.talkboard" :key="item.cs_id">
<ListItem :item="item"></ListItem>
</div>
...
</template>
<script>
import { reactive, onMounted, useContext } from '#nuxtjs/composition-api';
import axios from 'axios';
import Header from './header';
import ListItem from './list-item.vue';
export default {
name: 'ListHome',
components: {
Header,
ListItem,
},
setup() {
const state = reactive({
data: [],
});
const { store } = useContext();
const fatch = async () => {
....
};
onMounted(fatch);
return {
state,
fatch,
};
},
};
</script>
error message
TypeError: Cannot read property 'data' of undefined
I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
why error on this ?? because of composition API that define the state with reactive() function ??
In your test file maybe you can try something like this:
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data: () => {
return {
data: mockOrder,
};
},
});

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

Vue Test Utils mount in multiple tests

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