DEV Community

Julian Toscani
Julian Toscani

Posted on

TIL: Stubbing nested, global properties in Vitest

I recently upgraded some functionality of our legacy codebase to fit our new Stack containing:

  • Vue3/Nuxt3
  • Vite
  • Vitest
  • Typescript

The feature that had to be rewritten was a scroll-depth tracking. It extensively uses IntersectionObservers and some global properties like innerWidth or document.visibilityState to do some checks. As I had a hard time testing changes on document.visibilityState changes, I write this post so you won't have to deal with them.

TLDR;

vi.spyOn(document, 'visibilityState', 'get').mockImplementationOnce(() => 'hidden');
Enter fullscreen mode Exit fullscreen mode

First approach: simply setting it

Does not work as document.visibilityState is defined as read-only. It is, however a solution if you want to manipulate window.innerWidth as shown in the docs

Second Approach: Using global stubs

The first idea I had was to use global stubs. With these you can overwrite a global property or object and provide Mock to use instead. I used this to stub the IntersectionObservers as suggested in the docs.

Unfortunately, if you need the functionality of the original object somewhere else this won't work as it apparently gets hoisted, meaning it is not scoped to your current test. For me, this did not work. vi.unstubAllGlobals() did not reset the stubs which may have prevented this issue as we use JSDOM.

Third Approach: Using a spy

spys are functions which observe the use of methods. You can use them like so vi.spyOn(object, "methodName"). They are usefull in cases where you check if calling a function or method calls another function or method. This helped me to find out if the function I was testing was called. However, I needed to now if it was called with the correct argument.

Solution: mocking the return of a getter you spy on

After a bit of tinkering I found out, that vi.spy accepts a third argument and allows me to provide a mockImplementation for that setter/getter. The third parameter describes if the property - instead of being a method - is a getter or setter.

Hence I ended up with the following solution:

vi.spyOn(document, 'visibilityState', 'get').mockImplementationOnce(() => 'hidden');

// and later

vi.spyOn(document, 'visibilityState', 'get').mockImplementationOnce(() => 'visible');
Enter fullscreen mode Exit fullscreen mode

If you have questions, better ideas or find errors, please leave a comment.

Cheers!

Top comments (0)