DEV Community

Testing Svelte stores and mocking dependencies

Daniel Irvine πŸ³οΈβ€πŸŒˆ on January 12, 2020

We’ve covered off the basics and it’s time to get to the fun stuff. In this part of the series, we’ll move the fetch call from the CallbackCompone...
Collapse
 
jeffwscott profile image
Jeff Scott

Can you please offer some insight into how to make the localStorage available on the global window?

I import stores into my components, but they are failing because the localstorage that hydrates the store is not available on import.

I tried creating a localStorage object from here stackoverflow.com/a/26177872/6130344 and added it to global.window.localStorage in setupGlobalJsdom() with no luck :(

I see this is available, npmjs.com/package/jasmine-local-st..., but it looks like it's for Jasmine 2, and I'm not sure if importing it into svelte.js and adding it to global.window.localStorage is just the complete wrong way to go about this.

Any suggestion?

Collapse
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

I knocked this up just now:

github.com/dirv/svelte-testing-dem...

Actually kind of tricky, because writing a store in the conventional Svelte way, by calling writable at the top-level, means that the store is instantiated as soon as you import the file.

That means you need a loadX function so that you can separate out the definition of the store from the loading from localStorage. This function would need to be called somewhere when your application loads, which I haven’t shown.

That means you need separate values for notLoaded and notLoggedIn, in this example.

Also, Svelte subscribe calls the callback immediately on definition, with whatever value the store currently has.

Also, I don’t call unsubcribe anywhere... which is probably fine... subscribe was likely never designed to be used outside of a component.

Another way to do this: I could have called setItem directly after calling user.set rather than using subscribe.

Btw, this doesn’t answer the question of how to set up local storage for a component that uses this store. I haven’t tried it but I’d hope in that case you could call JSDOM’s non-stubbed getItem and then ensure that loadUser is called in your beforeEach.

Let me know if this helps!

Collapse
 
jeffwscott profile image
Jeff Scott

I appreciate you taking the time, I really hope this helps someone!

Here is a link to one of my custom stores:
github.com/Lamden/wallet/blob/mast...

I like them because they self hydrate from the localstorage. Also,, the value that is sent first is the correct one and not a placeholder; which means my components don't need to handle an empty value. I also don't need to call an initiate function anywhere in my app.

You are right that it seems there is no way around this in testing. I see what you have done with the user store and I like it as it works great for testing, but doesn't work for my situation as it seems localstorage doesn't get added to the window till later.

My app is a Chrome Extension, so I'm 100% sure the window will be there for my store when the app loads.

This may be one of those situations where I have to resort to manual testing!

Thread Thread
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

Another thing you can try is instantiating JSDOM before you import any test files. For Jasmine, you can create a helper (which is simply an file that’s loaded) and register it in spec/support/jasmine.json.

Jest does this implicitly because it has the notion of an ”environment” in which your tests run, and the standard environment is a JSDOM environment.

I don’t really like this approach as not all test files require JSDOM, but it might solve this problem? It’s worth trying.

Thread Thread
 
jeffwscott profile image
Jeff Scott

Would I then import the JSDOM into svelte.js? helpers don't seem to be ES6 compatible so this is causing me some syntax errors.

Thread Thread
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

Oh, of course. I should have thought of that. You’ll need to take that one function, create a new file with just that, and convert import -> require. I can try it myself tomorrow

Collapse
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

What version of JSDOM are you using? The latest version has an implementation of localStorage ready to go, I think.

You can also use spies but I wouldn’t bother with using a package for it (although that’s what I say about everything to be fair, so ymmv πŸ€·β€β™‚οΈ)

Here’s an example of how I used spies to test local storage in a React codebase (with Jest):

github.com/PacktPublishing/Masteri...

Collapse
 
jeffwscott profile image
Jeff Scott

So now I need to learn Jest or does Jasmine have spies, also I guess I need to find out what spies are lol.

I'm using whatever JSDOM you had in your dependency tree. I copied them out.

Thread Thread
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

Jasmine has spies - jasmine.createSpy

I’ll see if I can port across the local storage tests to Svelte a little later today.

Collapse
 
jeffwscott profile image
Jeff Scott

Me again :)

What is the purpose of removing the stubbing and adding rewire$fetch?

There is now no way to mock the return value of fetch which I thought was the point.

I think I may be a bit lost.

Collapse
 
jeffwscott profile image
Jeff Scott

I'm wondering if it's because you split the testing of the store out to it's own spec and you test the fetch in there. Then you don't need to retest that in the Component spec.. Is that correct?

Collapse
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

Correct :)

🀣

I think I confused this by aliasing fetch as fetchPrice, but leaving rewire$fetch instead of naming that rewire$fetchPrice.

Thanks for this feedback. I need to work on this a little!

Collapse
 
jeffwscott profile image
Jeff Scott

Under "Rewriting the CallbackComponent specs" am I right to assume that the "displays the initial price" test is not correct as per the implementation? The initial price should be set by the fetch call (which is stubbed to 99.99). By setting the store value directly you are just duplicating the next test case making them redundant (if set works it works).

Should it be re-written like so as to test being set by fetch?

it("displays the initial price", async () => {
mount(CallbackComponent);
await tick();
expect(container.textContent).toContain("The price is: $99.99");
});

Also, I seem to need like 5 "ticks" or else the test case will fail. Any idea why? is there a danger in using too many ticks?

Collapse
 
d_ir profile image
Daniel Irvine πŸ³οΈβ€πŸŒˆ

Thanks for pointing this out, I think I just did a poor job of explaining it so I’ll try to rewrite this to make it clearer.

This middle section is mid-refactor which is part of the trouble. Because I'm refactoring the code by extracting the fetch logic into a store, the tests for CallbackComponent should no longer care about fetch at all.

The test displays the initial price that doesn’t care so much about how the data is retrieved, it just cares that the Svelte subscription for price is set up correctly.

But the test with the set AND the fetch stub is a half-way house between the old version and the new version. The final section of this post removes the need to stub window.fetch by instead stubbing out fetchPrice instead. That is a good thing because the tests for CallbackComponent then have no knowledge of how fetchPrice works (i.e. by calling window.fetch, just like the component itself.

I’m still not sure if I’m doing a good job of describing this--let me know if this has helped!