document.execCommand is not a function in vue test utils - javascript

Vue testing with vue-test-utils with Jest, gives errors when exec copy command
document.execCommand is not a function.
How can I resolve it?
This is code snippet in component source.
copyToClipboard(){
document.execCommand('copy');
}
And here I had writen test like this;
import Vue from 'vue';
import Vuex from 'vuex';
import {shallowMount} from '#vue/test-utils'
describe('*****.vue', () => {
let wrapper;
beforeAll(()=>{
wrapper = shallowMount(***, {attachToDocument:true, store, localVue});
})
it('should render correct content', () => {
wrapper.vm.copyToClipboard();
);
}

Presumably you're using jsdom for the tests? In which case execCommand is not supported.
Jest doesn't support environments such as headless Chrome, but you may have some success using Puppeteer

Related

Cannot find module '#jest/globals' in a vue test using jest

I am trying to write a really simple test from vue documentation inside my Project.
I can run tests with jest in my project, but as soon as i am trying to mock axios request with jest, i have this error :
FAIL tests/unit/test.spec.js
● Test suite failed to run
Cannot find module '#jest/globals' from 'test.spec.js'
14 | })
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:259:17)
at _getJestObj (tests/unit/test.spec.js:16:7)
at Object.<anonymous> (tests/unit/test.spec.js:3:1)
Here is my Foo component :
<template>
<button #click="fetchResults">{{ value }}</button>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
value: null
}
},
methods: {
async fetchResults() {
const response = await axios.get('mock/service')
this.value = response.data
}
}
}
</script>
And the test associated :
import { shallowMount } from '#vue/test-utils'
import Foo from './Foo'
jest.mock('axios', () => ({
get: Promise.resolve('value')
}))
it('fetches async when a button is clicked', done => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
wrapper.vm.$nextTick(() => {
expect(wrapper.text()).toBe('value')
done()
})
}
Any help would be appreciated :)
Thanks guys !
Assuming that you are also using babel-jest, make sure you have both versions of jest and babel-jest set to same numer (24, 26` etc.). I had the same problem and it was because the package versions were not in sync.
If you're using npm 7+, then peer dependencies are automatically installed and you could end up with two different versions of babel-jest used simultaneously. You can disable that behavior by adding
legacy-peer-deps=true
into .npmrc file.
In my case, this error happens in babel-jest#26.0.1. After downgrade to babel-jest#21.2.0, problem disappear.

vue-test-utils: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value

I've looked at other answers with this problem, and it seems to be caused by trying to import vue-router into the test. This however, is not the case for my problem. Here is my test code:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import ListDetails from '../components/homepage/ListDetails'
import EntityList from '../components/homepage/EntityList'
import BootstrapVue from 'bootstrap-vue'
import Vuex from 'vuex'
import faker from 'faker'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
describe('ListDetails.vue', () => {
it('gets the correct page list when router list id param present', () => {
const selected_list = {
id: faker.random.number(),
name: faker.lorem.words(),
entries: []
}
testRouteListIdParam(selected_list)
})
})
Then in testRouteListIdParam, I have:
function testRouteListIdParam(selected_list) {
// just assume this sets up a mocked store properly.
const store = setUpStore(selected_list, true)
const $route = {
path: `/homepage/lists/${selected_list.id}`
}
const wrapper = mount(ListDetails, {
mocks: {
$route
},
store,
localVue
})
}
As soon as mount() happens, I get the error:
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
Any ideas why this would be happening? Again, I'm not using VueRouter anywhere in the unit tests, so I'm not sure why I'm getting the error. Could it be BootstrapVue or Vuex that are messing things up?
So this is a bug with vue-test-utils. If you are using VueRouter anywhere (even if it's not used in any unit test), you will get the above error.
A work around is to use process.env.NODE_ENV in your unit tests and set it to 'test', and wherever you're using VueRouter, check process.env.NODE_ENV like so:
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
at least until vue-test-utils bug is fixed, this should fix this problem
I think these docs are relevant to your situation:
Common gotchas
Installing Vue Router adds $route and $router as read-only properties on Vue prototype.
This means any future tests that try to mock $route or $router will fail.
To avoid this, never install Vue Router globally when you're running tests; use a localVue as detailed above.
The error you're seeing indicates that one of your components (and outside your test code) is installing VueRouter (e.g., Vue.use(VueRouter)).
To address the issue, search for the VueRouter installation in your component code path, including any of their imports, and refactor it so that the import is not required there. Typically, the installation of VueRouter exists only in main.js or its imports.
GitHub demo
I encountered this, and it was because I was importing vueRouter into a controller, outside of a vueComponent, where this.$router wasn't defined.
import router from '#/router';
router.push('/foo')
janedoe's answer can work but it's often risky to modify production code just to make some tests pass. I prefer to bypass the bug by doing this:
Run your test in watch mode
npx jest --watch src/components/Foo.test.ts
Locate Vue.use(VueRouter) in your code and diagnose what is the chain of components indirectly running the code by adding this just above
const error = new Error();
console.log(
error.stack
?.split('\n')
.filter((line) => line.includes('.vue'))
.join('\n'),
);
This logs a list of file path like
console.log
at Object.<anonymous> (/path/to/components/Bar.vue:1:1)
at Object.<anonymous> (/path/to/components/Foo.vue:1:1)
Chose a component in this list and, in your test file, mock it
jest.mock('/path/to/components/Bar.vue');

Mock react-dom Jest

I'm trying to mock the react-dom module using Jest
import React from 'react';
import {configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Link from '../components/DumbComp';
import { shallowToJson } from 'enzyme-to-json';
import { render } from 'react-dom';
configure({ adapter: new Adapter() });
jest.mock('react-dom');
describe('Link',() =>{
it('should render correctly', ()=>{
expect(render).toHaveBeenCalledWith(
<Link title="mockTitle" url="mockUrl" />, 'element-node'
);
expect(render).toHaveBeenCalledTimes(1);
});
});
When I run the test I get the following error:
Link › should render correctly
expect(jest.fn()).toHaveBeenCalledWith(expected)
Expected mock function to have been called with:
[<Link title="mockTitle" url="mockUrl" />, "element-node"]
But it was not called.
It seems that when I mock the render method it doesn't return anything. How can I correctly mock it?
I'm using this tutorial, see under the "The art of mocking" section.
If you want to create a manual mock create in the same file like this:
jest.mock('react-dom', () => ({
render: jest.fn(),
}));
I would recommend to have a look at snapshot and use it togheter with enyzme. It makes testing easier because you can write something like this:
describe ('Link',() =>{
it ('should render correctly', ()=> {
const component = mount(
<Link title="mockTitle" url="mockUrl" />
);
expect(component).toMatchSnapshot();
});
});
Which gives you a snapshot of how the component got rendered exactly. You can also use it with function you test and it will give you a snapshot of all the calls your function got called with and the arguments the function got called with.
You should create a mock file next to your node_modules in __mocks__/react-dom.js
// react-dom.js
export default {
render: jest.fn(),
};
The accepted answer might be a little out of date. To mock react-dom without side-effects you need to only mock the function you need, which in this case is the render function.
This can be done using Jest's requireActual like so:
jest.mock('react-dom', () => ({
...jest.requireActual('react-dom'),
render: jest.fn(),
}))

jquery doesn't work with jsdom/enzyme

I have a minimum test react app with following component:
import React from 'react';
import $ from 'jquery';
export default class App extends React.Component {
componentDidMount() {
console.log('componentDidMount', $('#helloDiv').length);
}
render() {
return <div id='helloDiv'>
Hello React!
</div>;
}
}
this works fine when loading it in browser (Chrome). The console.log() in componentDidMount() prints out 1 helloDiv element found
However, if I run the test using mocha + enzyme + jsdom, the same console.log() in App component prints out 0:
import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import App from '../src/client/app/first'
describe('first test', () => {
it('should pass', () => {
const app = mount(<App />);
expect(app.find('#helloDiv').length).to.eq(1);
});
});
Note: I don't have problem with this unit test, it's passing. The real problem is when < App /> is mounted using enzyme, componentDidMount() is called but the console.log() statement inside it prints out 0, instead of 1
Here is how I run mocha:
mocha --require enzyme/withDom --compilers js:babel-core/register test/index.test.js
Any idea why jquery selector doesn't find anything in the test? It should not be mocha issue because the same issue happens if I change to jest
Finally found the issue:
Enzyme mount(<SomeComponent />) by default will do full DOM rendering but not insert the rendered component into current document (JSDom). That's why jQuery cannot find any element in current document
To do full DOM rendering AND attach to current document:
mount(<SomeComponent />, { attachTo: document.getElementById('app') });
Where app is empty div available when jsdom is setup:
global.document = jsdom('<html><head></head><body><div id="app" /></body></html>');
There needs to be some setup done before you could jsdom with jquery in node-env.
Try this if it helps.
Create a test helper file like this -
test_helper.js
import _$ from 'jquery';
import jsdom from 'jsdom';
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = global.window.navigator;
const $ = _$(window);
chaiJquery(chai, chai.util, $);
export {expect};
While running -
mocha --require enzyme/withDom --compilers js:babel-core/register --require test/test_helper.js test/index.test.js
or another way use jsdom-global without test_helper.js file.
npm install --save-dev jsdom-global
Then :
import 'jsdom-global/register';
//at the top of file , even , before importing react
I couldn't get Phuong Nguyen's answer to work. I did find the relevant page in the enzyme docs. I ended up with something like, based on the final example on that page:
const div = global.document.createElement('div');
global.document.body.appendChild(graphDiv);
const wrapper = mount(<SomeComponent />, { attachTo: div }); // same as the other answer
// clean up after ourselves
wrapper.detach();
global.document.body.removeChild(div);

Import components for server-side rendering in ES6

I've got a nice little ES6 React component file (simplified for this explanation). It uses a library that is browser-specific, store This all works beautifully on the browser:
/app/components/HelloWorld.js:
import React, { Component } from 'react';
import store from 'store';
export default class HelloWorld extends Component {
componentDidMount() {
store.set('my-local-data', 'foo-bar-baz');
}
render() {
return (
<div className="hello-world">Hello World</div>
);
}
}
Now I'm trying to get it to render on the server as follows, using babel-register:
/server/routes/hello-world.js:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import HelloWorld from '../../app/components/HelloWorld'
export default function(req, res) {
res.render('root', {
reactHTML: ReactDOMServer.renderToString(<HelloWorld />),
});
}
I get an error from the node server saying "window is not defined" due to importing 'store'. Ideally I could conditionally import by detecting the environment (node vs browser), but conditional imports aren't supported in ES6.
What's best way to get around this? I don't actually need to execute the browser code (in this case componentDidMount won't be called by ReactDOMServer.renderToString) just get it running from node.
One way would be using babel-rewire-plugin. You can add it to babel-register via the plugins option
require('babel/register')({
plugins: ['babel-rewire-plugin']
});
Then rewire your store dependency to a mocked store:
HelloWorld.__Rewire__('store', {
set: () => {} // no-op
});
You can now render HelloWorld from the server peacefully.
If you want to suppress the load of some npm module, you can just mock it.
Put this on your node.js application setup, before the HelloWorld.js import:
require.cache[require.resolve('store')] = {
exports: {
set() {} // no-op
}
};
This value will be used instead of the real module, which doesn't need on your purposes. Node.js module API is stable, so this behavior will not be broken and you can rely on it.

Categories

Resources