I’m migrating a Vue 2.6 app to the Composition API (via the plugin) and hit two testing issues with vue-test-utils
(v1) + Jest:
Jest couldn’t mock functions from a plain TS module (not a Vue component).
Error: Cannot redefine property: ...
wrapper.setProps()
did not trigger Composition API watchers, so my side-effects wouldn’t run in tests.
"vue": "^2.6.10"
@vue/composition-api
(plugin)"@vue/test-utils": "^1.3.6"
v1 (legacy)"babel-jest": "^29.7.0"
"jest": "^29.7.0"
"jest-environment-jsdom": "^29.7.0"
"ts-jest": "^29.4.1"
Cannot redefine property
Trying to jest.mock()
a plain TS helper module caused Cannot redefine property
.
When mocking ES modules / TS transpiled output, Jest needs the __esModule
flag to handle the default/named export shape correctly. Without it, spreading the actual module or re-defining its properties can blow up.
Mark the mock as an ES module and spread the real implementation:
import * as utils from '@/path/to/your/component';
jest.mock('@/path/to/your/component', () => {
return {
__esModule: true, // <-- important
...jest.requireActual('@/path/to/your/component'),
};
});
Then you can spy as usual:
const updateGeoJSONSpy = jest.spyOn(utils, 'updateGeoJSON');
Original answer https://stackoverflow.com/a/72885576/1216480
setProps()
doesn’t trigger Composition API watch
in Vue 2 testsA watch
inside setup()
that depends on a prop didn’t run when I used wrapper.setProps(...)
.
With Vue 2 + Composition API plugin + vue-test-utils
1.3.6, prop updates via setProps
don’t reliably re-trigger watchers created inside setup()
. It’s a known limitation/quirk of the legacy stack.
Use wrapper.setData(...)
to update the reactive source that your watch
depends on. This updates component state without remounting and does trigger the watcher. (Note: onMounted
won’t run again, this is expected.)
const updateGeoJSONSpy = jest.spyOn(utils, 'updateGeoJSON');
const geojson = {
type: 'Feature',
geometry: { type: 'Point', coordinates: [10, 50] },
};
const wrapper = mount(MapDisplay, {
propsData: {
mapData: { geojson },
},
});
await wrapper.vm.$nextTick();
// initial call
expect(updateGeoJSONSpy).toHaveBeenCalled();
// mutate reactive data to trigger watch
await wrapper.setData({
mapData: {
geojson: {
type: 'Feature',
geometry: { type: 'Point', coordinates: [12, 15] },
},
},
});
expect(updateGeoJSONSpy).toHaveBeenCalledTimes(2);
Notes:
onMounted
).setProps
, but it doesn't work either!setData
was the only consistent approach for me on Vue 2 + Composition API + VTU 1.3.6.toRefs()
// MapDisplay.vue (sketch)
import Vue from 'vue';
import { defineComponent, watch } from '@vue/composition-api';
import { updateGeoJSON } from '@/components/WoIstMeinWald/utils';
export default defineComponent({
name: 'MapDisplay',
props: {
mapData: { type: Object, required: true },
},
setup(props) {
watch(
() => props.mapData?.geojson,
(val) => {
if (val) updateGeoJSON(val);
},
{ immediate: true }
);
return {};
},
});
__esModule: true
in your mock and/or rely on jest.requireActual
+ jest.spyOn
.vue-test-utils
, setProps
might not trigger watch
defined in setup()
. Use setData
to poke reactive state and fire the watcher.