Hereās a short Svelte component that displays the text Submitting...
when a button is clicked:
<script>
let submitting = false;
const submit = async () => {
submitting = true;
await window.fetch('/foo');
submitting = false;
}
</script>
<button on:click="{submit}" />
{#if submitting}
Submitting...
{/if}
Look carefully at the definition of submit
. The submitting
variable is set to true
before the call to window.fetch
and reset to false
after the call returns.
The text is only rendered when submitting
is true.
In other words, the Submitting...
text appears after the button is clicked and disappears after the window.fetch
call completes.
Why this is difficult to test
This behavior is tricky because one of our tests will need to get into the state where the Submitting...
text is displayed, and freeze in that state while our test runs its expectations. To do that we need to use Svelteās tick
function to ensure the rendered output is updatetd.
Writing the tests
We require three unit tests!
- That the
Submitting...
text appears when the button is clicked. - That initially, no text is displayed.
- That the
Submitting...
text disappears after thewindow.fetch
call completes.
Testing the text appears
Letās take a look at how weād test this.
The test below uses my Svelte testing harness which is just a few dozen lines of code. Iāve saved that at spec/svelteTestHarness.js
, and this test exists as spec/Foo.spec.js
.
For more information on how Iām running these tests, take a look at my guide to Svelte unit testing.
import expect from "expect";
import Foo from "../src/Foo.svelte";
import { setDomDocument, mountComponent, click } from "./svelteTestHarness.js";
import { tick } from "svelte";
describe(Foo.name, () => {
beforeEach(setDomDocument);
beforeEach(() => {
window.fetch = () => Promise.resolve({});
});
it("shows āSubmitting...ā when the button is clicked", async () => {
mountComponent(Foo);
click(container.querySelector("button"));
await tick();
expect(container.textContent).toContain("Submitting...");
});
});
Notice the use of tick
. Without that, this test wouldnāt pass. Thatās because when our code executes submitting = true
it doesnāt synchronously update the rendered output. Calling tick
tells Svelte to go ahead and perform the update.
Crucially, we havenāt yet flushed the task queue: calling tick
does not cause the fetch
promise to execute.
In order to make that happen, we need to flush the task queue which weāll do in the third test.
Testing initial state
First though we have to test the initial state. Without this test, we canāt prove that it was the button click that caused the text to appear: it could have been like that from the beginning.
it("initially isnāt showing the āSubmittingā text...", async () => {
mountComponent(Foo);
expect(container.textContent).not.toContain("Submitting...");
});
Testing the final state
Finally then, we check what happens after the promise resolves. We need to use await new Promise(setTimeout)
to do this, which flushes the ask queue.
it("hides the āSubmitting...ā text when the request promise resolves", async () => {
mountComponent(Foo);
click(container.querySelector("button"));
await new Promise(setTimeout);
expect(container.textContent).not.toContain("Submitting...");
});
And there it is. Three tests to prove a small piece of behavior. Although it might seem overkill for such a small feature, these tests are quick to writeāthat is, once you know how to write them š¤£
Checkout out my guide to Svelte unit testing for more tips on how to test Svelte.