A test double for Svelte 5 components (including non–runes mode components that are compiled with Svelte 5). It works with Vitest, Jest and Jasmine. It's a fork of svelte-component-double, modified to work with Svelte 5, remove private API use, and to convert to TypeScript.
Prerequisites:
- Node version as specified in package.json under the
volta.nodekey- Volta will automatically install and use the correct version of Node
This library is built using svelte-package:
npm install
npx svelte-packageThe built library can be found in the dist/ directory.
Create a file in your Vite project, named
test/registerSvelteComponentDouble.js, with the following content:
import { expect } from "vitest";
import * as matchers from "svelte-component-double/vitest";
expect.extend(matchers);
import { componentDouble } from "svelte-component-double";
globalThis.componentDouble = componentDouble;Then update your vi.config.js file to include this as a setup file for Vitest:
const config = {
...,
test: {
...,
setupFiles: [
...,
'./test/registerSvelteComponentDouble.js'
]
}
}Then you can begin to write mocked tests like this:
import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen } from "@testing-library/svelte";
import Child from "./Child.svelte";
import Parent from "./Parent.svelte";
vi.mock("./Child.svelte", async () => ({
default: componentDouble("Child"),
}));
describe("Parent component", () => {
beforeEach(Child.reset);
it("renders a Child with the right props", () => {
render(Parent);
expect(Child).toBeRenderedWithProps({
foo: "bar",
});
});
});Since mocks are active for the entire module, you may wish to place mocked tests
in a separate test file e.g. parent.mocks.test.js, keeping parent.test.js
free for non-mocked tests.
Add the following helper in spec/support/jasmine.json.
"helpers": [
"../node_modules/svelte-component-double/lib/matchers/jasmine.js"
]You'll need to use a mocking tool like babel-plugin-rewire-exports.
In the example below, Parent is the component under test, and Child is being
spied on.
import Child, { rewire as rewire$Child, restore } from "../src/Child.svelte";
import Parent from "../src/Parent.svelte";
import { componentDouble } from "svelte-component-double";
describe("Parent component", () => {
it("renders a Child", () => {
// ensure JSDOM is set up and ready to go
rewire$Child(componentDouble(Child));
const el = document.createElement("div");
new Parent({ target: el });
expect(Child).toHaveBeenCalled();
expect(el.querySelector(spySelector(Child))).not.toBeNull();
});
});The expect(component) function has the following matchers available.
| Matcher | Description |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------- |
| toBeRendered() | Passes if there was at least one instance of the component instantiated in the current document. |
| toBeRenderedIn(container) | Same but with a specific DOM container instead of | document.body. |
| toBeRenderedWithProps(props) | Passes if there was at least one instance of the component instantiated with these exact props. |
| toBeRenderedWithPropsIn(props, container) | Same as above but with a specic DOM container instead of document.body. |
A spied/stubbed component will be rendered into the DOM like this:
<div class="spy-ComponentName" id="spy-ComponentName-instanceNumber" />You can use the selector function to return a selector that will match all
instances of a rendered double. So for the example above, calling
spySelector(Child) will return "div[class=spy-Child]".
You can use the instanceSelector(n) to return a selector that matches a
specific instance of the component.
If your stubbed component uses a two-way binding you can trigger that binding to
update using the updateBoundValue function.
For example, say you have the component TagList which can be used like this:
<TagList bind:tags="{tags}" />Then you can test how your component responds to updates to tags like this:
rewire$TagList(componentDouble(TagList));
mount(<your component that uses TagList>);
TagList.firstInstance().updateBoundValue(
component, "tags", ["a", "b", "c"]);If your component exports methods, you'll need to stub these manually on the
mounted component instance. To make this easier, the componentDouble function
accepts an onInit callback, which will be called with each newly-constructed
component instance.
For example, with Vitest:
<!-- MyComponent.svelte -->
<script>
export function doSomething() {
console.log('Did something!');
}
</script>// In your test file:
import MyComponent from "./MyComponent.svelte";
vi.mock("./MyComponent.svelte", async () => ({
default: componentDouble("MyComponent", {
onInit: (component, _props) => {
component.doSomething = vi.fn();
},
}),
}));
it("Calls the component's doSomething method", () => {
expect(MyComponent.lastInstance().doSomething).toHaveBeenCalledOnce();
});All of these functions are available on your component double type.
| Property/function | Type | Description |
|---|---|---|
instances |
Array of instances | Each instance of the component that has been rendered. |
reset() |
Function | Resets a mocked component between tests. Call this in your beforeEach block. |
selector() |
Function | Selector for all instances of this double. |
instanceSelector(n) |
Function | Selector for a single instances of this double. |
findMatching(matchFn) |
Function | Find the call whose props match the matchFn predicate |
firstInstance() |
Function | Returns the first instance of a component, which you an then manipulate using functions such as updateBoundValue (see note above). |
lastInstance() |
Function | Returns the last instance of a component. |
getInstanceFromElement(domElement) |
Function | Returns the component instance that rendered the given DOM element. |
mountedInstances() |
Function | Returns only the instances that are currently rendered in the DOM. |
propsOfAllInstances() |
Function | Returns an array for props for each of the rendered instance of this component. |
dispatch(event) |
Function | Dispatches an event to the last rendered component. You can also call dispatch on individual instances. |
All contributions are welcome. Please feel free to create issues or raise PRs.