Jest : TypeError: Cannot read property 'variable' of undefined - vue.js

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

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.

vuex unknown mutation type: setPatient

I'm using vue 3 with composition api and vuex 4, I've done it this way before but now its throwing that error.
Here's my store/index.js
import { createStore } from "vuex";
export const store = new createStore({
state: {
patient: [],
},
mutations: {
setPatient(state, payload) {
state.patient = payload;
},
},
getters: {
getPatient(state) {
return state.patient;
},
getAppointment(state) {
return state.patient.appointments;
},
},
})
app.js
require('./bootstrap');
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
import {store} from './Store'
const { RayPlugin } = require('vue-ray');
window.$ = window.jQuery = require("jquery");
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => require(`./Pages/${name}.vue`),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(store)
.use(RayPlugin, { interceptErrors: true, host: '127.0.0.1', port: 23517 })
.mixin({ methods: { route } })
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });
And following the documentation, on my component I did the following:
import { useStore } from 'vuex'
import {onMounted, reactive, ref} from "vue";
export default {
props: {
patient: {
type: Object,
required: true
}
},
setup(props) {
const store = useStore();
onMounted(() => {
store.commit('setPatient', props.patient);
})
}
}
So far I've done this before, but using the composition api is new for me, so I couldn't find where the error is

Vue: Test utils vue composition - root.$route

Versions
Test utils
1.0.3
Vue
2.6.11
Vue router
3.20
Vue compsotion API
1.0.0-beta.8
I have some difficulties developing a component test because of root.$route.
In the component, I have a watch, which will be aware when the route changes, in order to change some variables of the component.
My component
<script>
export default {
setup ( _, { root }) {
// behavior
...
watch( () => root.$route,
({ query, meta }) => {
foo = 'bar';
}, {
immediate: true
});
}
};
</script>
My test
import { shallowMount, createLocalVue } from '#vue/test-utils';
import VueCompositionAPI from '#vue/composition-api';
import Component from '#/components/bundles/layout/Component';
import router from '#/router/';
const localVue = createLocalVue();
localVue.use( VueCompositionAPI );
localVue.use( router );
describe( 'Component', () => {
it( 'Insert', () => {
const wrapper = shallowMount( Component, {
mocks: {
$t: () => ({})
},
localVue,
router
});
console.log( wrapper.html() );
});
});
Error
TypeError: Cannot destructure property 'query' of 'undefined' as it is undefined.
I've tried to mock the router, but without effect.
I tried in the following ways:
1:
...
describe( 'Component', () => {
it( 'Insert', () => {
const wrapper = shallowMount( Component, {
mocks: {
$t: () => ({}),
$route: { query: '', meta: '' }
},
localVue,
router
});
console.log( wrapper.html() );
});
});
2:
import router from '#/router/';
jest.mock( '#/router/', () => ({
currentRoute: {
meta: {},
query: {}
},
push: jest.fn( () => Promise.resolve() )
}) );
Some help?

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.js test-utils Why my onSubmit function mock is not called?

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