How to test components with dynamic imports in vue-test-utils and jest - unit-testing

I am testing a component which dynamically imports the child components. Here a screenshot of that section
Inside Parent.vue
Error
It works fine for normal imports but not with dynamic async import. Can anyone help on how configure Jest to support async component imports?

If problem is still relevant (I didn't found anydirect answer) try to 'stub' components which should be imported.
Component
<template>
<div>
<dynamic-imported-component-one id="componentOne"></dynamic-imported-component-one>
<dynamic-imported-component-two id="componentTwo"></dynamic-imported-component-two>
</div>
</template>
<script>
const ComponentOne = resolve => import(/* webpackChunkName: "views/view-ComponentOne-vue" */ '../Components/ComponentOne.vue');
const ComponentTwo = resolve => import(/* webpackChunkName: "views/view-ComponentTwo-vue" */ '../Components/ComponentTwo.vue');
export default {
components: {
'dynamicImportedComponentOne': ComponentOne,
'dynamicImportedComponentTwo': ComponentTwo }
}
</script>
Test:
describe('SomeComponent.vue', () => {
const stubs = {
dynamicImportedComponentOne: '<h3>Stubbed component one</h3>',
dynamicImportedComponentTwo: '<h3>Stubbed component one</h3>'
}
it('test for SomeComponent', () => {
const wrapper = shallowMount(SomeComponent, { stubs });
expect(wrapper.find('#componentOne').exists()).toBeTruthy();
expect(wrapper.find('#componentTwo').exists()).toBeTruthy();
});
});
You can't test the ComponentOne/ComponentTwo content, but it should be anyway done in separate tests.

Did you try using "dynamic-import-node" in you .babelrc ? It seemed to fix dynamic import of child components in a mounted parent for me. I got the pointer from https://github.com/enzymejs/enzyme/issues/1460#issuecomment-355193587

Related

Jest: Cannot read property of undefined when importing from own package nextjs

Got this weird bug when running the jest test, one of the UI component from a self defined UI package keeps throwing error, saying that an object in that package is undefined...
The component itself works perfectly fine, and the same component's testing logic works in another repo without nextjs, and that repo utilize #swc/jest for js transform in jest.config file.
I've also added that package itself to transformIgnorePatterns in jest-config file, but somehow the bug still presents...
The project itself is in nextjs, and below is a snapshot of the jest.config file
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
testPathIgnorePatterns: [
'<rootDir>/.next/',
'<rootDir>/node_modules/',
'<rootDir>/e2e/'
],
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['#testing-library/jest-dom/extend-expect'],
setupFiles: [require.resolve('whatwg-fetch')],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }]
},
transformIgnorePatterns: ['/node_modules/myPackage', 'jest-runner'],
testMatch: ['**/*.spec.{js,jsx,ts,tsx}'],
};
and the error itself goes Error: Uncaught [TypeError: Cannot read property 'object' of undefined], which tracks down to /node_modules/myPackage
how the package is used
import { InputBox } from 'myPackage';
const MyComponent = () => {
return (
<div>
<InputBox />
</div>
);
}
export default MyComponent;
and here's the test:
import { act, render } from '#testing-library/react';
import React from 'react';
describe('show component', () => {
it('should render', async () => {
await act(async () => {
render(
<MyComponent/>
);
});
});
});
I've found a similar question on stackoverflow Jest: Cannot read property of undefined when importing from own package
but that one is using regular js, and this one being next.js, there's really nowhere I can update .babelrc to update those configs...
Any input would be appreciated.
Update: it turns out the component that causes me bug is built on top of react-popper library, which is built on top of popper.js library. popper.js library doesn't support jsdom by default, and it requires to do jest mock. But my library is 2 layers abstractions on top of popper.js library, I'm not sure how to do that, or even if that is doable...

Why won't Trix editor mount in Vue component when running tests with Jest?

I built a simple Vue component that wraps the Trix editor. I'm trying to write tests for it, but Trix doesn't seem to mount properly and isn't generating a toolbar element like it does in the browser. I'm using Jest test runner.
TrixEdit.vue
<template>
<div ref="trix">
<trix-editor></trix-editor>
</div>
</template>
<script>
import 'trix'
export default {
mounted() {
let el = this.$refs.trix.getElementsByTagName('trix-editor')[0]
// HACK: change the URL field in the link dialog to allow non-urls
let toolbar = this.$refs.trix.getElementsByTagName('trix-toolbar')[0]
toolbar.querySelector('[type=url]').type = 'text'
// insert content
el.editor.insertHTML(this.value)
el.addEventListener('trix-change', e => {
this.$emit('input', e.target.innerHTML)
})
}
}
</script>
TrixEdit.spec.js
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import TrixEdit from '#/components/TrixEdit.vue'
const localVue = createLocalVue()
localVue.config.ignoredElements = ['trix-editor']
describe('TrixEdit', () => {
describe('value prop', () => {
it('renders text when value is set', () => {
const wrapper = mount(TrixEdit, {
localVue,
propsData: {
value: 'This is a test'
}
})
expect(wrapper.emitted().input).toEqual('This is a test')
})
})
})
The expect() fails with the following error
Expected value to equal:
"This is a test"
Received:
undefined
at Object.toEqual (tests/unit/TrixEdit.spec.js:19:39)
Why is Trix not initializing in my test?
trix-editor is not mounting mainly because MutationObserver is not supported in JSDOM 11, and attachToDocument was not used. There were several other bugs in the test described below.
GitHub demo w/issues fixed
Missing MutationObserver and window.getSelection
Vue CLI 3.7.0 uses JSDOM 11, which doesn't support MutationObserver, needed by the Custom Elements polyfill to trigger the connectedCallback. That lifecycle hook normally invokes trix-editor's initialization, which would create the trix-toolbar element that your test is trying to query.
Solution 1: In your test, import mutationobserver-shim before TrixEdit.vue, and stub window.getSelection (called by trix and currently not supported by JSDOM):
import 'mutationobserver-shim' // <-- order important
import TrixEdit from '#/components/TrixEdit.vue'
window.getSelection = () => ({})
Solution 2: Do the above in a Jest setup script, configured by setupTestFrameworkScriptFile:
Add the following property to the config object in jest.config.js (or jest in package.json):
setupTestFrameworkScriptFile: '<rootDir>/jest-setup.js',
Add the following code to <rootDir>/jest-setup.js:
import 'mutationobserver-shim'
window.getSelection = () => ({})
Missing attachToDocument
#vue/test-utils does not attach elements to the document by default, so trix-editor does not catch the connectedCallback, which is needed for its initialization.
Solution: Use the attachToDocument option when mounting TrixEdit:
const wrapper = mount(TrixEdit, {
//...
attachToDocument: true, // <-- needed for trix-editor
})
Premature reference to trix-editor's editor
TrixEdit incorrectly assumes that trix-editor is immediately initialized upon mounting, but initialization isn't guaranteed until it fires the trix-initialize event, so accessing trix-editor's internal editor could result in an undefined reference.
Solution: Add an event handler for the trix-initialize event that invokes the initialization code previously in mounted():
<template>
<trix-editor #trix-initialize="onInit" />
</template>
<script>
export default {
methods: {
onInit(e) {
/* initialization code */
}
}
}
</script>
Value set before change listener
The initialization code adds a trix-change-event listener after the value has already been set, missing the event trigger. I assume the intention was to also detect the first initial value setting in order to re-emit it as an input event.
Solution 1: Add event listener first:
<script>
export default {
methods: {
onInit(e) {
//...
el.addEventListener('trix-change', /*...*/)
/* set editor value */
}
}
}
</script>
Solution 2: Use v-on:trix-change="..." (or #trix-change) in the template, which would remove the setup-order problem above:
<template>
<trix-editor #trix-change="onChange" />
</template>
<script>
export default {
methods: {
onChange(e) {
this.$emit('input', e.target.innerHTML)
},
onInit(e) {
//...
/* set editor value */
}
}
}
</script>
Value setting causes error
The initialization code sets the editor's value with the following code, which causes an error in test:
el.editor.insertHTML(this.value) // causes `document.createRange is not a function`
Solution: Use trix-editor's value-accessor, which performs the equivalent action while avoiding this error:
el.value = this.value
I can confirm the problem lies in the not-so-ideal polymer polyfill included inside trix lib. I did an experiment to force apply the polyfill, then I can reproduce the same error TypeError: Cannot read property 'querySelector' of undefined even inside chrome browser env.
Further investigation narrows it down to the MutationObserver behavior difference, but still haven't got to the bottom.
Way to reproduce:
TrixEdit.vue
<template>
<div ref="trix">
<trix-editor></trix-editor>
</div>
</template>
<script>
// force apply polymer polyfill
delete window.customElements;
document.registerElement = undefined;
import "trix";
//...
</script>

How to set or test this.$parent in Vuejs unit testing?

In my component set
data(){
categories: this.$parent.categories => which I set in main.js
}
Code file main.js
import categories from '../config/categories';
new Vue({
router,
data: {
categories: categories
}
});
I created 1 function unit test
it(‘check component is a button’,() => {
const wrapper = shallow(FormSearch);
expect(wrapper.contains(‘button’)).toBe(true);
});
I run test then show error: Error in data(): "TypeError: Cannot read property ‘categories’ of undefined"
How to fix it. Help me.
Why not import you categories config file directly into your component?
import Categories from '../config/categories'
then your data method can directly access it:
data () { return { categories: Categories }}
You'll find that much easier to test
yes, thanks you. I changed. I run test then it happens error other
Cannot find module '../../config/categories' from 'mycomponet.vue'.
Although. I run project on browser just working well.
How to fix it. Thanks you very much
For Testing , you can set mock data to escape undefined error while testing . But it is not standard solution .....
it(‘check component is a button’,() => {
const wrapper = shallow(FormSearch);
let mockCategories = { // mock category data }
wrapper.$parent = {
categories: mockCategories
}
expect(wrapper.contains(‘button’)).toBe(true);
});
Try this approach:
const Parent = {
data: () => ({
val: true
}),
template: '<div />'
}
const wrapper = shallowMount(TestComponent, {
parent: Parent
})

How to Test a Global Event Bus With Vue Test Utils?

I am trying to learn how to test events emitted through a global Event Bus. Here's the code with some comments in the places I don't know what to do.
// EvtBus.js
import Vue from 'vue';
export const EvtBus = new Vue();
<!-- CouponCode.vue -->
<template>
<div>
<input
class="coupon-code"
type="text"
v-model="code"
#input="validate">
<p v-if="valid">
Coupon Redeemed: {{ message }}
</p>
</div>
</template>
<script>
import { EvtBus } from '../EvtBus.js';
export default {
data () {
return {
code: '',
valid: false,
coupons: [
{
code: '50OFF',
discount: 50,
message: '50% Off!'
},
{
code: 'FREE',
discount: 100,
message: 'Entirely Free!'
}
]
};
},
created () {
EvtBus.$on('coupon-applied', () => {
//console.info('had a coupon applied event on component');
});
},
methods: {
validate () {
// Extract the coupon codes into an array and check if that array
// includes the typed in coupon code.
this.valid = this.coupons.map(coupon => coupon.code).includes(this.code);
if (this.valid) {
this.$emit('applied');
// I NEVER see this on the coupon-code.spec.js
EvtBus.$emit('coupon-applied');
}
}
},
computed: {
message () {
return this.coupons.find(coupon => coupon.code === this.code).message;
}
}
}
</script>
// tests/coupon-code.spec.js
import expect from 'expect';
import { mount } from '#vue/test-utils';
import CouponCode from '../src/components/CouponCode.vue';
import { EvtBus } from '../src/EvtBus.js';
describe('Reminders', () => {
let wrp;
beforeEach(() => {
wrp = mount(CouponCode);
});
it('broadcasts the percentage discount when a valid coupon code is applied', () => {
let code = wrp.find('input.coupon-code');
code.element.value = '50OFF';
code.trigger('input');
console.log(wrp.emitted('applied'));
//
// I NEVER see this on the outpout.
// How can I test it through a global event bus rather than
// an event emitted from the component instance?
//
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
// Passes, but not using EvtBus instance.
expect(wrp.emitted('applied')).toBeTruthy;
});
});
So, my doubt is how to test that the global event bus is emitting and listening to events inside components that use that event bus.
So, is it possible to test the global Event Bus using Vue Test Utils or I should use another approach?
If component is using global EventBus, eg that's imported outside of given component and assigned to window.EventBus, then it's possible to use global Vue instance to redirect $on or $emit events to wrapper's vm instance. That way you can proceed writing tests as if component is emitting via this.$emit instead of EventBus.$emit:
it('clicking "Settings" button emits "openSettings"', () => {
global.EventBus = new Vue();
global.EventBus.$on('openSettings', (data) => {
wrapper.vm.$emit('openSettings', data);
});
// component emits `EventBus.$emit('openSettings')`
expect(wrapper.emitted('openSettings')).toBeTruthy(); // pass
});
Well,
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
This code in your spec file won't be called because the mounted wrp component is not using the same EvtBus you are importing in your spec file above.
What you require to test this is an npm package named inject-loader so that you can provide your own implementation(stub) of the EvtBus dependency of your coupon code component.
Somewhat like this
const couponCodeInjector = require('!!vue-loader?inject!src/views/CouponCode');
const stubbedModules = {
'../EvtBus.js': {
$on : sandbox.spy((evtName, cb) => cb());
}
};
const couponCode = couponCodeInjector(stubbedModules);
and then in your unit test you can assert whether the stubbedModules['../EvtBus.js'].$on has been called or not when code.trigger('input');
PS: I haven't used vue-test-utils. So I don't know exactly how to the stubbing with this npm package.
But the main thing you need to do is to find a way to stub your EvtBus dependency in the CouponCode component in such a way that you can apply a spy on it and check whether that spy has been called or not.
Unit tests should focus on testing a single component in isolation. In this case, you want to test if the event is emitted, since that is the job of CouponCode.vue. Remember, unit tests should focus on testing the smallest units of code, and only test one thing at a time. In this case, we care that the event is emitted -- EventBus.test.js is where we test what happens when the event is emitted.
Noe that toBeTruthy is a function - you need (). expect(wrp.emitted('applied')).toBeTruthy is actually not passing, since you need () - at the moment, it is actually doing nothing -- no assertion is made.
What your assertion should look like is:
expect(wrp.emitted('applied')).toBeTruthy()
You can go one step further, and ensure it was only emitted once by doing something like expect(wrp.emitted().applied.length).toBe(1).
You then test InputBus in isolation, too. If you can post the code for that component, we can work through how to test it.
I worked on a big Vue app recently and contributed a lot to the main repo and documentation, so I'm happy to help out wherever I can.
Let me know if that helps or you need more guidance. If possible, post EventBus.vue as well.
I got the same issue with vue-test-utils and Jest. For me, createLocalVue() of vue-test-utils library fixed the issue. This function creates a local copy of Vue to use when mounting the component. Installing plugins on this copy of Vue prevents polluting the original Vue copy. (https://vue-test-utils.vuejs.org/api/options.html#localvue)
Adding this to your test file will fix the issue:
const EventBus = new Vue();
const GlobalPlugins = {
install(v) {
// Event bus
v.prototype.$bus = EventBus;
},
};
// create a local instance of the global bus
const localVue = createLocalVue();
localVue.use(GlobalPlugins);
jest.mock('#/main', () => ({
$emit: jest.fn(),
}));
Include this in code in your spec file at the very begining.
Note: '#/main' is the file from which you are importing Event Bus.

How to Unit Test React-Redux Connected Components?

I am using Mocha, Chai, Karma, Sinon, Webpack for Unit tests.
I followed this link to configure my testing environment for React-Redux Code.
How to implement testing + code coverage on React with Karma, Babel, and Webpack
I can successfully test my action and reducers javascript code, but when it comes to testing my components it always throw some error.
import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils'; //I like using the Test Utils, but you can just use the DOM API instead.
import chai from 'chai';
// import sinon from 'sinon';
import spies from 'chai-spies';
chai.use(spies);
let should = chai.should()
, expect = chai.expect;
import { PhoneVerification } from '../PhoneVerification';
let fakeStore = {
'isFetching': false,
'usernameSettings': {
'errors': {},
'username': 'sahil',
'isEditable': false
},
'emailSettings': {
'email': 'test#test.com',
'isEmailVerified': false,
'isEditable': false
},
'passwordSettings': {
'errors': {},
'password': 'showsomestarz',
'isEditable': false
},
'phoneSettings': {
'isEditable': false,
'errors': {},
'otp': null,
'isOTPSent': false,
'isOTPReSent': false,
'isShowMissedCallNumber': false,
'isShowMissedCallVerificationLink': false,
'missedCallNumber': null,
'timeLeftToVerify': null,
'_verifiedNumber': null,
'timers': [],
'phone': '',
'isPhoneVerified': false
}
}
function setup () {
console.log(PhoneVerification);
// PhoneVerification.componentDidMount = chai.spy();
let output = TestUtils.renderIntoDocument(<PhoneVerification {...fakeStore}/>);
return {
output
}
}
describe('PhoneVerificationComponent', () => {
it('should render properly', (done) => {
const { output } = setup();
expect(PhoneVerification.prototype.componentDidMount).to.have.been.called;
done();
})
});
This following error comes up with above code.
FAILED TESTS:
PhoneVerificationComponent
✖ should render properly
Chrome 48.0.2564 (Mac OS X 10.11.3)
Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Tried switching from sinon spies to chai-spies.
How should I unit test my React-Redux Connected Components(Smart Components)?
A prettier way to do this, is to export both your plain component, and the component wrapped in connect. The named export would be the component, the default is the wrapped component:
export class Sample extends Component {
render() {
let { verification } = this.props;
return (
<h3>This is my awesome component.</h3>
);
}
}
const select = (state) => {
return {
verification: state.verification
}
}
export default connect(select)(Sample);
In this way you can import normally in your app, but when it comes to testing you can import your named export using import { Sample } from 'component'.
The problem with the accepted answer is that we are exporting something unnecessarily just to be able to test it. And exporting a class just to test it is not a good idea in my opinion.
Here is a neater solution without the need of exporting anything but the connected component:
If you are using jest, you can mock connect method to return three things:
mapStateToProps
mapDispatchToProps
ReactComponent
Doing so is pretty simple. There are 2 ways: Inline mocks or global mocks.
1. Using inline mock
Add the following snippet before the test's describe function.
jest.mock('react-redux', () => {
return {
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent
}),
Provider: ({ children }) => children
}
})
2. Using file mock
Create a file __mocks__/react-redux.js in the root (where package.json is located)
Add the following snippet in the file.
module.exports = {
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent,
}),
Provider: ({children}) => children
};
After mocking, you would be able to access all the above three using Container.mapStateToProps,Container.mapDispatchToProps and Container.ReactComponent.
Container can be imported by simply doing
import Container from '<path>/<fileName>.container.js'
Hope it helps.
Note that if you use file mock. The mocked file will be used globally for all the test cases(unless you do jest.unmock('react-redux')) before the test case.
Edit: I have written a detailed blog explaining the above in detail:
http://rahulgaba.com/front-end/2018/10/19/unit-testing-redux-containers-the-better-way-using-jest.html
You can test your connected component and I think you should do so. You may want to test the unconnected component first, but I suggest that you will not have complete test coverage without also testing the connected component.
Below is an untested extract of what I do with Redux and Enzyme. The central idea is to use Provider to connect the state in test to the connected component in test.
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import SongForm from '../SongForm'; // import the CONNECTED component
// Use the same middlewares you use with Redux's applyMiddleware
const mockStore = configureMockStore([ /* middlewares */ ]);
// Setup the entire state, not just the part Redux passes to the connected component.
const mockStoreInitialized = mockStore({
songs: {
songsList: {
songs: {
songTags: { /* ... */ }
}
}
}
});
const nullFcn1 = () => null;
const nullFcn2 = () => null;
const nullFcn3 = () => null;
const wrapper = mount( // enzyme
<Provider store={store}>
<SongForm
screen="add"
disabled={false}
handleFormSubmit={nullFcn1}
handleModifySong={nullFcn2}
handleDeleteSong={nullFcn3}
/>
</Provider>
);
const formPropsFromReduxForm = wrapper.find(SongForm).props(); // enzyme
expect(
formPropsFromReduxForm
).to.be.deep.equal({
screen: 'add',
songTags: initialSongTags,
disabled: false,
handleFormSubmit: nullFcn1,
handleModifySong: nullFcn2,
handleDeleteSong: nullFcn3,
});
===== ../SongForm.js
import React from 'react';
import { connect } from 'react-redux';
const SongForm = (/* object */ props) /* ReactNode */ => {
/* ... */
return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
....
</form>
};
const mapStateToProps = (/* object */ state) /* object */ => ({
songTags: state.songs.songTags
});
const mapDispatchToProps = () /* object..function */ => ({ /* ... */ });
export default connect(mapStateToProps, mapDispatchToProps)(SongForm)
You may want to create a store with pure Redux. redux-mock-store is just a light-weight version of it meant for testing.
You may want to use react-addons-test-utils instead of airbnb's Enzyme.
I use airbnb's chai-enzyme to have React-aware expect options. It was not needed in this example.
redux-mock-store is an awesome tool to test redux connected components in react
const containerElement = shallow((<Provider store={store}><ContainerElement /></Provider>));
Create fake store and mount the component
You may refer to this article Testing redux store connected React Components using Jest and Enzyme | TDD | REACT | REACT NATIVE
Try creating 2 files, one with component itself, being not aware of any store or anything (PhoneVerification-component.js). Then second one (PhoneVerification.js), which you will use in your application and which only returns the first component subscribed to store via connect function, something like
import PhoneVerificationComponent from './PhoneVerification-component.js'
import {connect} from 'react-redux'
...
export default connect(mapStateToProps, mapDispatchToProps)(PhoneVerificationComponent)
Then you can test your "dumb" component by requiring PhoneVerification-component.js in your test and providing it with necessary mocked props. There is no point of testing already tested (connect decorator, mapStateToProps, mapDispatchToProps etc...)