Any way to avoid memory leaks with shallowMount in vue-test-utils? - vue.js

We have a custom unit testing setup for vue that works on Node, Mocha and jsdom package which simulates browser environment (no webpack, karma). We have wrote about 3k specs already (big app with hundreds of components), and now when mocha is running it becomes slower and slower, and eventually the process just hangs. We thought that maybe there is a memory leak in "jsdom", so we changed it to a "domino" (alternative package), but it still hangs.
We checked the heap memory usage and it just keeps growing (up to 1.5 GB!).
So we think that the problem is with either vue or vue-test-utils. It looks like each time we use mount/shallowMount it needs to be destroyed/unmounted after each test to release memory?
Any ideas? Thanks in advance!

The best way i found is to set the wrapper to null after the test suit
example below using mocha
describe(" View ", () => {
let wrapper;
beforeEach() {
wrapper = mount(Com.Vue, { localVue }) ;
});
after( ()=> {
wrapper = null ;
});
});
It made a huge difference in my case was having memory leaks of about 8gb after running test multiple times now using about 300mb
JavaScript has automatic memory management and garbage collection. If you get rid of all references to a piece of data, the memory will be reclaimed
Hope this helps
Thanks for voting

make sure that you call wrapper.destroy(); in afterEach method and if you use mount or shallowMount in tests bodies call wrapper.destroy(); before mount the new Vue instance.
it works for me.
describe(" View ", () => {
let wrapper;
beforeEach() {
wrapper = mount(Com.Vue, { localVue }) ;
});
afterEach(() => {
wrapper.destroy();
});
});

Related

Detox with Split.IO

I found a jest mock for split.io for react-native. I am now trying to use this mock so that I do not receive network timeouts because split.io is trying to sync in the background. Here is the mock:
jest.mock('#splitsoftware/splitio-react-native', () => {
const splitio = jest.requireActual('#splitsoftware/splitio-react-native');
return {
...splitio,
SplitFactory: () => {
return splitio.SplitFactory({
core: {
authorizationKey: 'localhost',
},
// Mock your splits and treatments here
features: {},
sync: {
localhostMode: splitio.LocalhostFromObject(),
},
});
},
};
});
I currently put this in my detox init.js file, but it doesn't seem to be doing anything. The only way, so far, I have been able to get my tests to run is to just immediately destroy my SplitFactory as soon as I create it (not through the mock). Obviously, this isn't ideal since I'd have to change the code every time I wanted to run it. I tried creating a .mock.ts file, but that also didn't get read, and when I tried to adjust my metro.config.js, it just failed to run at all. Does anyone have any ideas of how I can get this to run properly in detox for iOS, or have experience with this?
I had the same issue with split.io and detox, when a particular split.io would block my tests indefinitely. The only work around i found was
await device.disableSynchronization();
found here

VueJS and ElectronJS | VueJS stops rendering completely when I import ipcRenderer in a component

So I am coding a VueJS and ElectronJS template which can be found here: https://github.com/dev-aethex/electronjstemplate
My code works something like this,
Inside of my Vue component I access a global pre constructed class called MainProcessInterface and when it's constructed it first checks if vue is compiled for running in a development server. If it's in a dev server it will connect to the dev socket which electrons main process will host if electron is in dev mode and not compiled. This method seems to be working great, I had to use a socket because vue dev server is being loaded into electron via loadURL and so vue has no clue what ipcRenderer is. Inside the main process interface, if vue is compiled it will instead use the ipcRenderer.send() method. This is were the problem was born.
As soon as Vue runs thought the TS code, it sees ipcRenderer.send and freaks out while printing an error to the electron window console saying fs.existsSync does not exist or is defined.
I can't find a way around this. I though maybe i'll split MainProcessInterface into 2 peices, one for ipc and the other for websockets. Although it isn't a very good way, so before implementing it, I would like to know if there is a better more proper way of doing such.
I had a similar issue with React. Are you importing the ipcRenderer object somewhere in your build process? You might want to make sure it references the correct variable. I tried to drop this in as a comment but it wouldn't fit:
//index.html (index.ejs) for me... This is in the main HTML entry point
var IPC = null;
try {
IPC = require('electron').ipcRenderer;
console.log('IPC IS: ' + IPC)
} catch (err) {
console.log('CRICITCAL ERROR: IPC NOT ENABLED')
console.log(err)
IPC = null;
}
Then I initialize off that context in React with a startup here:
setTimeout(()=>{
console.log('----------------HACK FIRED POST REHYDRATE')
window.REDUX_STORE.dispatch(
(dispatch, getState) => {
const _state = getState()
if(window.IPC) {
if(_state.osc && _state.osc.on) {
dispatch( reconnectToEos() )
} else {
dispatch( updateStatus('[OSC Startup: DISCONNECTED]', ))
}
console.log('\t------------ELECTRON')
} else {
//Shut off OSC
dispatch( updateOscKey('on', false) )
dispatch( updateStatus('[WebApp, OSC disabled]', ))
console.log('\t------------WEB')
}
}
)
}, 1000)
Basically I'm using a global variable (window.IPC) to initialize my app so I don't import a bad variable in my build process. I have a fair number of Electron APIs where this alleviates the issues with building via Webpack.
I hope this helps!

Snapshot Testing with Vue-Router

I'm trying to run jest-snapshot tests in a Vue app created using the Vue-cli app. When I test a component that have a router-link in it, I receive the following warning.
[Vue warn]: Unknown custom element: <router-link> - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
Following the documentation here Unit tests with View Router I can stub out the router-link, but that doesn't pass my snapshot test. Has anyone run into this issue before?
Updated: A more consistent solution
tl;dr:
The solution I found that works consistently is to create a localVue (usually outside the scope of the describe() blocks) and add the RoutherLinkStub as a component.
import { mount, RouterLinkStub, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.component('router-link', RouterLinkStub);
const wrapper = mount(Component, { localVue });
The documentation doesn't make the solution obvious because it seems to be something of a hybrid of the page you linked to and this one about RouterLinkStub.
From that page:
import { mount, RouterLinkStub } from '#vue/test-utils'
const wrapper = mount(Component, {
stubs: {
RouterLink: RouterLinkStub
}
})
That solution works in most cases. If you have a case where you need to use mount and your router-link is a not in the component being tested but the one below it, you will still get that warning. Now, if you are in that situation, it's worth reflecting if you're writing your test properly (are you testing too much rather than a small unit?), but I have a situation or two where if I shallow instead of mount, the snapshot test is worthless because it renders children as <!----> when I callexpect(wrapper.html()).toMatchSnapshot()`.
A Note on Snapshot Tests:
I haven't actually seen examples of people using the Wrapper.html() method for snapshot testing, but it really seems like the best solution in my experimenting. vue-server-renderer is a long walk for what appears to be the same result. Accessing the vm property of Wrapper gives you access to the $el property, without much trouble, but it's just a more whitespace-heavy rendering of html().

AsyncStorage is not returning the callback

I am using redux-persist in a react native project, that runs just fine in a broad number of devices except Android 7. I am trying to debug the problem on why my local storage is nor persisting and found this:
The following code executes inside React Native component lifecycle's
componentDidMount() {
attachObservables(store)
setInterval(async () => {
console.log('Inside setInterval')
const data = await AsyncStorage.getAllKeys()
console.log('inside the getAllKeys')
data.forEach(async k => {
const value = await AsyncStorage.getItem(k)
console.group(k)
console.log(value)
console.groupEnd()
})
}, 3000)
}
Code after 'Inside setInterval' is never called. It only runs once if outside the setInterval. If I call once the code outside the setInterval it appears to run just fine. I also tried callback format vs async / await version but it does not seem to matter.
Same problem I had using firebase js library (callbacks never return after the first one). I am now looking for alternatives to workaround the problem.
Any ideas?
As of React Native 0.51 in some Android versions, the runtime can get blocked by other native modules, impeding the resolution of the mentioned methods.
It can be fixed via https://github.com/facebook/react-native/issues/14101#issuecomment-345563563, ensuring this methods use a free thread from the thread pool.
A PR has been submitted and I hope that will be released in future versions. If you want it to use this fix right now, it is available here https://github.com/netbeast/react-native
EDIT:
I am not experiencing this anymore on real devices over react-native#0.53, anyhow others have also reported, so the issue is still open.

Does Jest reset the JSDOM document after every suite or test?

I'm testing a couple of components that reach outside of their DOM structure when mounting and unmounting to provide specific interaction capability that wouldn't be possible otherwise.
I'm using Jest and the default JSDOM initialization to create a browser-like environment within node. I couldn't find anything in the documentation to suggest that Jest reset JSDOM after every test execution, and there's no explicit documentation on how to do that manually if that is not the case.
My question is, does Jest reset the JSDOM instance after every test, suite or does it keep a single instance of JSDOM across all test runs? If so, how can I control it?
To correct the (misleading) accepted answer and explicitly underline that very important bit of information from one of the previous comments:
No. Jest does not clean the JSDOM document after each test run! It only clears the DOM after all tests inside an entire file are completed.
That means that you have to manually cleanup your resources created during a test, after every single test run. Otherwise it will cause shared state, which results in very subtle errors that can be incredibly hard to track.
The following is a very simple, yet effective method to cleanup the JSDOM after each single test inside a jest test suite:
describe('my test suite', () => {
afterEach(() => {
document.getElementsByTagName('html')[0].innerHTML = '';
});
// your tests here ...
});
Rico Pfaus is right, though I found that resetting the innerHTML the way he suggests was too destructive and caused tests to fail. Instead, I found selecting a specific element (by class or id) I want to remove from the document more effective.
describe('my test suite', () => {
afterEach(() => {
document.querySelector(SOME CLASS OR ID).innerHTML = ''
})
})
This is still an issue for many people — and it's the top answer in Google — so I wanted to provide some context from the future ;)
does it keep a single instance of JSDOM across all test runs
Yes, the jsdom instance remains the same across all test runs within the same file
If so, how can I control it?
Long story short: you'll need to manage DOM cleanup yourself.
There is a helpful Github issue on facebook/jest that provides more context and solutions. Here's a summary:
if you want a new jsdom instance then separate your tests into separate files. This is not ideal for obvious reasons...
you can set .innerHTML = '' on the HTML element as mentioned in the accepted answer. That will resolve most issues but the window object will remain the same. Window properties (like event listeners) can persist in subsequent tests and cause unexpected errors.
cleanup the jsdom instance between tests. The jsdom cleanup function doesn't do anything magic — it's basically resetting global properties. Here's an example directly from the Github issue:
const sideEffects = {
document: {
addEventListener: {
fn: document.addEventListener,
refs: [],
},
keys: Object.keys(document),
},
window: {
addEventListener: {
fn: window.addEventListener,
refs: [],
},
keys: Object.keys(window),
},
};
// Lifecycle Hooks
// -----------------------------------------------------------------------------
beforeAll(async () => {
// Spy addEventListener
['document', 'window'].forEach(obj => {
const fn = sideEffects[obj].addEventListener.fn;
const refs = sideEffects[obj].addEventListener.refs;
function addEventListenerSpy(type, listener, options) {
// Store listener reference so it can be removed during reset
refs.push({ type, listener, options });
// Call original window.addEventListener
fn(type, listener, options);
}
// Add to default key array to prevent removal during reset
sideEffects[obj].keys.push('addEventListener');
// Replace addEventListener with mock
global[obj].addEventListener = addEventListenerSpy;
});
});
// Reset JSDOM. This attempts to remove side effects from tests, however it does
// not reset all changes made to globals like the window and document
// objects. Tests requiring a full JSDOM reset should be stored in separate
// files, which is only way to do a complete JSDOM reset with Jest.
beforeEach(async () => {
const rootElm = document.documentElement;
// Remove attributes on root element
[...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));
// Remove elements (faster than setting innerHTML)
while (rootElm.firstChild) {
rootElm.removeChild(rootElm.firstChild);
}
// Remove global listeners and keys
['document', 'window'].forEach(obj => {
const refs = sideEffects[obj].addEventListener.refs;
// Listeners
while (refs.length) {
const { type, listener, options } = refs.pop();
global[obj].removeEventListener(type, listener, options);
}
// Keys
Object.keys(global[obj])
.filter(key => !sideEffects[obj].keys.includes(key))
.forEach(key => {
delete global[obj][key];
});
});
// Restore base elements
rootElm.innerHTML = '<head></head><body></body>';
});
For those interested, this is the soft-reset I'm using in "jest.setup-tests.js" which does the following:
Removes event listeners added to document and window during tests
Removes keys added to document and window object during tests
Remove attributes on <html> element
Removes all DOM elements
Resets document.documentElement HTML to <head></head><body></body>
— #jhildenbiddle