Tips for writing great Svelte tests

Daniel Irvine šŸ³ļøā€šŸŒˆ - Jan 17 '20 - - Dev Community

In the last part in my series on Svelte testing, Iā€™ll round off with some smaller pieces of advice.

To see all the techniques used in this series, remember to check out the demo repo on GitHub.

GitHub logo dirv / svelte-testing-demo

A demo repository for Svelte testing techniques

Ā Focus on behavior, not static data

Remember why we should unit test? Hereā€™s one simple way of saying it: to avoid overtesting.

Overtesting is when you have multiple tests that cover the same surface area. It can result in brittle tests that simulatenously break when you make changes, which then slows you down as you fix all those tests.

But there are times when unit testing is overkill.

The biggest of those times is when you have static data, not data which changes.

Take, for example, a version of the Menu component that was introduced in the previous part on testing context. The version we looked at had a button that, when clicked, would open up the menu overlay.

But what if weā€™d like to draw a hamburger icon in place of the button. Menu.svelte might end up looking like this:

<button on:click={toggleMenu} class="icon">
  <svg
    viewBox="0 0 100 100"
    width="32"
    height="32">
    <!-- draw a hamburger! -->
    <rect x="10" y="12.5" width="80" height="15" rx="3" ry="3" />
    <rect x="10" y="42.5" width="80" height="15" rx="3" ry="3" />
    <rect x="10" y="72.5" width="80" height="15" rx="3" ry="3" />
  </svg>
</button>
{#if open}
<div class="overlay" on:click={toggleMenu}>
  <div class="box" on:click={supressClick}>
    <slot />
  </div>
</div>
{/if}
Enter fullscreen mode Exit fullscreen mode

Look at that svg there. Itā€™s a lot of declarative, static data, to draw a 32x32 hamburger.

Any unit test you write is essentially going to repeat whatā€™s written here: the test would verify that the HTML looks exactly like it does here.

I donā€™t write those tests. I see it as duplication. If HTML is static and never changes, I donā€™t use unit tests. If the system has a good set of system tests, then I may write them there instead.

But often I just wonā€™t write them. Iā€™m lazy.

This is a very different attitude from people who do not write unit tests at all. Often people donā€™t write unit tests because they feel that itā€™s too much work. In actual fact, writing unit tests but avoiding overtesting gives you less work but still gives you confidence in your code.

But now what if I wanted to introduce the ability for the caller to set their own Menu icon, by providing a named slot icon?

<button on:click={toggleMenu} class="icon">
  <slot name="icon">
    <svg
      viewBox="0 0 100 100"
      width="32"
      height="32">
      <rect x="10" y="12.5" width="80" height="15" rx="3" ry="3" />
      <rect x="10" y="42.5" width="80" height="15" rx="3" ry="3" />
      <rect x="10" y="72.5" width="80" height="15" rx="3" ry="3" />
    </svg>
  </slot>
</button>
Enter fullscreen mode Exit fullscreen mode

Now there is behavior. The behavior is that the svg doesnā€™t get drawn if an icon slot is defined, and does get drawn if an icon slot isnā€™t defined.

In this case I would test it, but possibly only as far as saying ā€œrenders an svg element when no icon slot is providedā€ rather than testing each individual rect.

(By the way, Iā€™d test that with an isolated component).

Raising events

Another time that behavior is important is when raising (or firing) DOM events, like click, input, submit and so on.

One of the big changes I noticed when moving from React to Svelte is that textboxes respond to input events rather than change events.

const changeValue = (element, value) => {
  const evt = document.createEvent("Event", { target: { value } });
  evt.initEvent("input", true, false);
  element.value = value;
  element.dispatchEvent(evt);
};
Enter fullscreen mode Exit fullscreen mode

The way I deal with events is to define little helper methods like the one above, changeValue, which can be used like this:

changeValue(nameField(), "your name");
Enter fullscreen mode Exit fullscreen mode

Some events, but not all, will need to have Svelteā€™s tick method called.

The library svelte-testing-library has a bunch of these methods already defined. I tend to write little functions to fire the events that I need (more on that below).

Why I donā€™t use svelte-testing-library

Three reasons:

  1. I think itā€™s overkill,
  2. I think itā€™s too opinionated
  3. I think building it yourself is a good way to learn

The kinds of helpers that make your tests clear are often very short, simple methods, as Iā€™ve shown in this series. They can often be written in less than 50 lines of code.

I think some of the language thatā€™s used around testing can be toxic. There are many, many tools and techniques to testing. For me personally, choosing a tool like any of the testing-library libraries feels like lock-in. Not just to the library, but also the opinionated ways of testing.

Iā€™ve learnt a HUGE amount about Svelte just by figuring all this stuff out, and by writing this course. Two months ago, I knew no Svelte. Now I feel like Iā€™ve nailed it. If I had made use of svelte-testing-library that most likely wouldnā€™t be true.

About the best reason Iā€™ve heard to use a framework rather than rolling your own is that itā€™s anti-social, meaning itā€™s fine if youā€™re an individual developer, but the moment you work on a team of professionals, this kind of behavior just doesnā€™t fly. Everyone has to spend time learning your hand-crafted methods, and everyone has to spend time maintaining them. By using a library, itā€™s someone elseā€™s problem.

Using object factories

A final tip. I use factory-bot to build example objects for my tests. It keeps my test suites clean and tidy. For example, hereā€™s spec/factories/post.js:

import { factory } from "factory-bot";

factory.define("post", () => ({}), {
  id: factory.sequence("Post.id", n => `post-${n}`),
  attributes: {
    content: factory.chance("paragraph"),
    tags: factory.chance("sentence")().toLowerCase().slice(0, -1).split(" ")
  }
});

export const post = () => factory.attrs("post");
Enter fullscreen mode Exit fullscreen mode

Conclusion

Thatā€™s it for this series. If youā€™ve enjoyed it, please consider following me and retweeting the tweet below to share the series with others.

Iā€™ll no doubt continue to post here about Svelte as I learn more about it and how to build professional applications with it.

All feedback is welcome, even if youā€™ve hated this series and disagreed with everything! Send it my way, please please please! I can only improve with practice and feedback!šŸ™

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player