Mock javascript function imported from vue - javascript

I'm trying to set up tests for a vue component collector which is importing a Javascript module to collect some data.
components/collector.vue
<script>
import dataService from '#/services/dataService.js';
...
dataService.collectResults().then(results => {
this.data = results;
});
</script>
The javascript file:
services/dataService.js
let dataService = {
collectResults() {
[...]
return dataPromise;
}
[...]
}
export default dataService;
I'm using jest for the tests for the collector component, and trying to mock the collectResults method.
collector.spec.js
jest.mock('dataService');
import { mount } from '#vue/test-utils';
import collector from '#/components/collector.vue';
import VueRouter from 'vue-router';
describe('behaviour tests', () => {
let cmp;
beforeEach(() => {
Vue.use(VueRouter);
const router = new VueRouter();
cmp = mount(collector, {
router
});
it('dummy test', () => {
console.log(cmp.vm.data);
}
services/__mocks__/dataService.js
const dataServiceMock = jest.genMockFromModule('dataService');
function __collectResults() {
return new Promise(() => {
console.log('mocked!');
[...]
});
}
dataServiceMock.collectResults = __collectResults;
export default dataServiceMock;
However, when running the tests the original dataService.js file is being executed (which in my case leads to an exception).
What should be the correct approach to this case, considering that I've to mock ~10 functions while leaving others as they are?

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!

VueJS / Jest - Testing if an imported function is called from a component's method

I have a VueJS 2 component that looks something like this:
<template>
<div>
<button #click="onFavorite">
Add to favorites
</button>
</div>
</template>
<script>
import { trackFavorite } from "#/utils/analytics";
export default {
name: "FavoriteButton",
methods: {
onFavorite() {
trackFavorite("click", "favorite");
[ ... ]
}
}
}
</script>
I want to write a Jest test that checks that when onFavorite is run trackFavorite is called. Tried something like this:
import { shallowMount } from '#vue/test-utils';
import FavoriteButton from '../FavoriteButton'
describe("FavoriteButton", () => {
let wrapper
beforeEach(() => {
wrapper = shallowMount(FavoriteButton)
})
describe('.onFavorite', () => {
beforeEach(() => {
wrapper.vm.trackFavorite = jest.fn()
wrapper.vm.onFavorite()
})
it('calls trackFavorite', () => {
expect(wrapper.vm.trackFavorite).toHaveBeenCalled()
})
})
})
But it doesn't work as trackFavorite is not replaced by the Jest mock function.
Use jest.mock() at the top of the test file to mock the entire import (including its methods).
require() the file within the test to access the mock.
With the mock reference, verify the mocked trackFavorite method is called.
// FavoriteButton.spec.js
import { shallowMount } from '#vue/test-utils'
import FavoriteButton from '#/components/FavoriteButton.vue'
jest.mock('#/utils/analytics') 1️⃣
describe('FavoriteButton.vue', () => {
it('calls trackFavorite on button click', async () => {
const analytics = require('#/utils/analytics') 2️⃣
const wrapper = shallowMount(FavoriteButton)
await wrapper.find('button').trigger('click')
expect(analytics.trackFavorite).toHaveBeenCalled() 3️⃣
})
})
demo

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.

Why is Vue not defined here?

Why am I getting Vue is not defined as an error here:
export default {
state: {
projects: {
loading: true,
failed: false,
lookups: [],
selectedId: 0
}
},
mutations: {
loadingProjectLookups (state, payload) {
state.projects.loading = true;
state.projects.failed = false;
}
},
actions: {
loadProjectLookups (context) {
return new Promise((resolve) => {
// VUE NOT DEFINED HERE:
Vue.http.get('https://my-domain.com/api/projects').then((response) => {
context.commit('updateProjectLookups', response.data);
resolve();
},
response => {
context.commit('failedProjectLookups');
resolve();
});
});
}
}
}
This is my vue config:
'use strict';
import Vue from 'vue';
import Vuex from 'vuex';
var VueResource = require('vue-resource');
/* plugins */
Vue.use(Vuex);
Vue.use(VueResource);
/* stores */
import importPageStore from './apps/import/import-page-store';
/* risk notification import */
import ImportApp from './apps/import/import-app.vue';
if (document.querySelector("#import-app")) {
var store = new Vuex.Store(importPageStore);
new Vue({
el: '#import-app',
store,
render: h => h(ImportApp)
});
}
My understanding is that Vue is defined globally and I cannot see why it is not defined. If I add import Vue from 'vue' to my store then I get a message that http is not defined. So I need to work out why Vue appears not to be available globally as I shouldn't have to do this.
I am using webpack to build my vue components. I have other pages rendered using this methodology and they work just fine. But this one does not? I am honestly stumped as to why as I cannot see any differences. The page renders and works. I can see that Vue is working. How can it be undefined?
In a component, you can use this.$http, however, in your store you will need to import Vue every time.
What you can do, is create a service folder and import Vue there. Then just reference your service in the store file.
There's an example here https://github.com/vuejs/vuex/issues/85
Which suggests something like this:
/services/auth.js
import Vue from 'vue'
export default {
authenticate(request) {
return Vue.http.post('auth/authenticate', request)
.then((response) => Promise.resolve(response.data))
.catch((error) => Promise.reject(error));
},
// other methods
}
In your store file:
import { AUTHENTICATE, AUTHENTICATE_FAILURE } from '../mutation-types'
import authService from '../../services/auth'
export const authenticate = (store, request) => {
return authService.authenticate(request)
.then((response) => store.dispatch(AUTHENTICATE, response))
.catch((error) => store.dispatch(AUTHENTICATE_FAILURE, error));
}
// other actions
This is how VueResource extends Vue prototype.
Object.defineProperties(Vue.prototype, {
// [...]
$http: {
get() {
return options(Vue.http, this, this.$options.http);
}
},
// [...]
});
}
VueResource handles the promise itself. Thus, you don't need to wrap the requests in promises. You can use Promise.all() later. But I don't see multiple requests so you just use the get request.
Reference: Using promise in vue-resource
I hope, this would solve your issue with that error.

proxyquire TypeError when not replacing every function in a module

I'm trying to use proxyquire to unit test my Redux reducers. I need to replace the functionality of one function in my test but keep the original functionality of the other, which is possible according to proxyquire's docs.
formsReducer.test.js:
import { expect } from 'chai';
import * as types from '../constants/actionTypes';
import testData from '../data/TestData';
import proxyquire from 'proxyquire';
describe('Forms Reducer', () => {
describe('types.UPDATE_PRODUCT', () => {
it('should get new form blueprints when the product changes', () => {
//arrange
const initialState = {
blueprints: [ testData.ipsBlueprint ],
instances: [ testData.basicFormInstance ]
};
//use proxyquire to stub call to formsHelper.getFormsByProductId
const formsReducerProxy = proxyquire.noCallThru().load('./formsReducer', {
'../utils/FormsHelper': {
getFormsByProductId: () => { return initialState.blueprints; }
}
}).default;
const action = {
type: types.UPDATE_PRODUCT,
stateOfResidence: testData.alabamaObject,
product: testData.basicProduct
};
//act
const newState = formsReducerProxy(initialState, action);
//assert
expect(newState.blueprints).to.be.an('array');
expect(newState.blueprints).to.equal(initialState.blueprints);
});
});
});
formsReducer.js:
import * as types from '../constants/actionTypes';
import objectAssign from 'object-assign';
import initialState from './initialState';
import formsHelper from '../utils/FormsHelper';
export default function formsReducer(state = initialState.forms, action) {
switch (action.type) {
case types.UPDATE_PRODUCT: {
let formBlueprints = formsHelper.getFormsByProductId(action.product.id);
formBlueprints = formsHelper.addOrRemoveMnDisclosure(formBlueprints, action.stateOfResidence.id);
return objectAssign({}, state, {blueprints: formBlueprints, instances: []});
}
}
I need to replace the functionality of formsHelper.getFormsByProductId() but keep the original functionality of formsHelper.addOrRemoveMnDisclosure() - as you can see in the proxyquire block I'm only replacing the getFormsByProductId() function. However, when I do this get the following error: TypeError: _FormsHelper2.default.addOrRemoveMnDisclosure is not a function. Looks to be a problem either with babel or with my export default for FormHelper.
The export for the FormsHelper looks like this:
export default class FormsHelper { ...methods and whatnot }.
How can I fix this problem?

Categories

Resources