Skip to content

onMount isn't called when rendering components  #222

@connerdassen

Description

@connerdassen

When I render a simple component like this:

Test.svelte:

<script>
    import { onMount } from "svelte";
    onMount(() => console.log("Mounted"));
</script>

<span>Hello World</span>

and render it through render(Test);

It never prints "Mounted" to the console. Why is this, and how can I test onMount?

Activity

yanick

yanick commented on Jun 8, 2023

@yanick
Collaborator

It does for me.

You might have to add a await tick() after the render, as the onMount is called after the component is rendered, according to the docs.

connerdassen

connerdassen commented on Jun 9, 2023

@connerdassen
Author

@yanick

It still doesn't print anything for me even after adding await tick(), if I add a top level console.log that does print, it's purely the onMount function...
I'm using Vitest, I don't know if that would make a difference

yanick

yanick commented on Jun 9, 2023

@yanick
Collaborator

[Vitest] It shouldn't affect anything.

Hmmm... It still works for me. Can you create a repo with the minimal amount of code that reproduce the problem?

yanick

yanick commented on Jun 9, 2023

@yanick
Collaborator

(also, I'll drop offline for a few days starting tomorrow. If I don't answer, I'm not ghosting, I'm just without Internet. :-) )

connerdassen

connerdassen commented on Jun 9, 2023

@connerdassen
Author

@yanick
I've uploaded an example to https://github.com/connerdassen/svelte-test-example
I asked in the official svelte discord server and was told

The render method mounts Svelte components to jsdom, which does not invoke lifecycle methods like onMount. Lifecycle methods are on the list of things to avoid when testing: https://testing-library.com/docs/#what-you-should-avoid-with-testing-library

So now I wonder why it works for you...

yanick

yanick commented on Jun 9, 2023

@yanick
Collaborator

So now I wonder why you it works for you...

Oooh, I'm using happydom, which might be why. I'll try to, uh, try with jsdom later on to see if it makes a difference.

yanick

yanick commented on Jun 9, 2023

@yanick
Collaborator

So with vitest, no console print, with jest, I get one. As for why, I haven't the foggiest. O.o

connerdassen

connerdassen commented on Jun 11, 2023

@connerdassen
Author

@yanick I have discovered something, when importing onMount from "svelte" it does not run, but when importing from "svelte/internal" it does...

skokenes

skokenes commented on Jun 12, 2023

@skokenes

I am hitting this bug as well. This seems like a bug, not WAD?

I understand you should not test internal lifecycle events, but thats not what we are trying to do here. Even if you are just trying to test the user-facing surface area of a component, if your component internally uses onMount, then you need that to run in order for the user to see the right thing so you can test the output.

Also, I've had no success using vitest with either jest or happy-dom. In both environments, onMount never runs

yanick

yanick commented on Jun 13, 2023

@yanick
Collaborator
yanick

yanick commented on Jun 13, 2023

@yanick
Collaborator
connerdassen

connerdassen commented on Jun 13, 2023

@connerdassen
Author

@skokenes seems to have figured it out in the svelte server, I'll reiterate:

Since the test is running in a Node environment instead of a browser environment, it uses the "node" exports from svelte which points to the ssr import path: https://github.com/sveltejs/svelte/blob/master/package.json

For SSR, life cycle methods do not run and are exported as a noop: https://github.com/sveltejs/svelte/blob/3bc791bcba97f0810165c7a2e215563993a0989b/src/runtime/ssr.ts#L1

Importing from "svelte/internal" bypasses this.

Solutions are either mocking onMount in every test:

vi.mock("svelte", async () => {
    const actual = await vi.importActual("svelte") as object;
    return {
        ...actual,
        onMount: (await import("svelte/internal")).onMount
    };
});

Or a custom alias in vite.config.ts:

resolve: process.env.TEST ? {
  alias: [{
    find: /^svelte$/,
    replacement: path.join(__dirname, "node_modules/svelte/index.mjs") 
   }]
} : {};
yanick

yanick commented on Jun 13, 2023

@yanick
Collaborator
paoloricciuti

paoloricciuti commented on Jun 27, 2023

@paoloricciuti

Excellent! At the very least that should be added to the docs. Thanks for capturing the info in this thread!
On Tue, 13 Jun 2023, at 6:10 AM, connerdassen wrote: @skokenes https://github.com/skokenes seems to have figured it out in the svelte server, I'll reiterate: Since the test is running in a Node environment instead of a browser environment, it uses the "node" exports from svelte which points to the ssr import path: https://github.com/sveltejs/svelte/blob/master/package.json For SSR, life cycle methods do not run and are exported as a noop: https://github.com/sveltejs/svelte/blob/3bc791bcba97f0810165c7a2e215563993a0989b/src/runtime/ssr.ts#L1 Importing from "svelte/internal" bypasses this. Solutions are either mocking onMount in every test: vi.mock("svelte", async () => { const actual = await vi.importActual("svelte") as object; return { ...actual, onMount: (await import("svelte/internal")).onMount }; });
Or a custom alias in vite.config.ts: resolve: process.env.TEST ? { alias: [{ find: /^svelte$/, replacement: path.join(__dirname, node_modules/svelte/index.mjs) }] } : {};

— Reply to this email directly, view it on GitHub <#222 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAE34UJ3YF3PY5VDPOT3YLXLA4C7ANCNFSM6AAAAAAY75IQUM. You are receiving this because you were mentioned.Message ID: @.***>

Just for info: svelte/internal got removed in svelte 4 so I think despite the workaround this should be fixed...if you have any suggestion on where to start to look I can see If I can craft a PR

26 remaining items

hardingjam

hardingjam commented on Jul 16, 2024

@hardingjam

@yanick I think we're good to officially close this one out 🧹

  • The docs now have setup instructions to ensure Vitest is properly configured to load Svelte's browser bundler
  • The docs now have an onMount FAQ entry
  • onMount tests have been added to our suite here

@dominikg if you have a minute, could you check over my work above? I compared the resolve.conditions: ['browser'] approach with the suggested plugin + unshift approach and got identical results - a resolve.conditions array with browser prepended to the conditions added by VPS and Vitest. I'd be really curious to know if I'm misunderstanding something here!

I have an existing test suite with some components that use onMount.
Adding the example from the FAQ to vite.config.js breaks the tests on any components that import modules using require().

Error: require() of ES Module ... is not supported.

Is there a way to selectively ignore the resolve option on a case-by-case basis?

mcous

mcous commented on Jul 16, 2024

@mcous
Collaborator

@hardingjam no, the Vite configuration is all or nothing. You could try one of the other options listed above rather than the plugin.

Using require in Svelte modules seems suspicious, though. Are you able to use import instead and configure Vite to prebundle your CJS dependencies into ESM?

karbica

karbica commented on Sep 5, 2024

@karbica

This is still an issue when testing functions that call onMount which are intended to be used inside components. Even with the first solution provided by @mcous.

sveltejs/svelte#13136 (comment)

The motivation for testing functions that contain onMount is because they can contain runes and lifecycle calls all encapsulated and then be used in any components - exactly like React hooks.

From what I recall, @testing-library provides ways to test React hooks in isolation and I'm trying to do exactly that with Svelte 5.

The issue is reproduced here: https://stackblitz.com/edit/vitejs-vite-2fltxz?file=vite.config.ts

mcous

mcous commented on Sep 5, 2024

@mcous
Collaborator

Hey @karbica, onMount cannot be used outside a .svelte component. This is not a Svelte Testing Library thing, this is just a Svelte thing. onMount relies on the implicit environment brought along by the Svelte component to function. It is inherently not encapsulated.

The way React Testing Library's hooks testing works is by wrapping the hook under test up in a fixture component. You can do the same to test things that require a component environment to function:

<!-- use-mouse-coords.test.svelte (or whatever name you want) -->
<script lang="ts">
import { useMouseCoords } from '../src/lib/use-mouse-coords.svelte.ts';

export const mouseCoords = useMouseCoords()
</script>
import { it, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';

import WithUseMouseCoords from './use-mouse-coords.test.svelte';

it('runs correctly when rendering a component', () => {
  const { component } = render(WithUseMouseCoords);

  expect(component.mouseCoords.x).toBe(0);
  expect(component.mouseCoords.y).toBe(0);
});

Personally, I find these fixture components annoying in my own tests. It's not really unit testing, and instead is integration testing the whole stack of my logic, the Svelte compiler, the Svelte runtime, and whichever DOM library the test suite is using (or the browser). If I find myself in a situation where I "need" to use a fixture component, I try to restructure my code so the logic I care about is more directly testable and avoid coupling to the view library/framework.

(One final bit of housekeeping: I see that you've added both the svelteTesting plugin to your Vitest config as well as the resolve.conditions customization from earlier in this thread. You do not need both, because the svelteTesting plugin sets the resolve conditions for you. See #359 if you're curious!)

karbica

karbica commented on Sep 5, 2024

@karbica

Thank you @mcous for the great write up.

This is not a Svelte Testing Library thing, this is just a Svelte thing. onMount relies on the implicit environment brought along by the Svelte component to function. It is inherently not encapsulated.

I agree and follow this entirely. This isn't for Testing Library to solve, it's a consequence of the framework. I was curious if there was any remedy this library could provide. The fixture component (which is also performed for React hooks) is exactly what came to mind for these kinds of functions in Svelte. I'm not a fan of the ceremony involved and it breaks away from the concept of unit testing, as you mentioned. Thanks for clearing that up and confirming that.

One final bit of housekeeping: I see that you've added both the svelteTesting plugin to your Vitest config as well as the resolve.conditions customization from earlier in this thread. You do not need both, because the svelteTesting plugin sets the resolve conditions for you. See #359 if you're curious!

Thanks for calling this out. I really didn't like how the escape hatch was leaking into the configuration file. Good to know this library is handling that for us.

I appreciate you taking a look into my concern.

Stadly

Stadly commented on Nov 25, 2024

@Stadly
  1. Manually force Vitest to load the browser version of with a resolve alias

    • Not very future proof; locks your config to the internals of the Svelte package
    • More targeted than option (1)
    • If you're considering this option because the browser condition breaks some other dependency, consider swapping that dependency with an alias instead, because it's probably less important than your Svelte dependency
import path from 'node:path'

import { svelte } from '@sveltejs/vite-plugin-svelte'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [svelte()],
  test: {
    environment: 'jsdom',
    alias: [
      {
        find: /^svelte$/,
        replacement: path.join(
          __dirname,
          './node_modules/svelte/src/runtime/index.js'
        ),
      },
    ],
  },
})

@mcous Thanks for your great research and writeup! You suggest swapping dependencies broken by the browser condition with an alias. I'm experiencing this with @auth/sveltekit. How can I swap it using an alias?

dominikg

dominikg commented on Nov 25, 2024

@dominikg

for svelte5, i made an example using vitests workspace feature here: https://github.com/dominikg/vitest-example-svelte5

note that this uses vitest.workspace.ts instead of .config.ts and extends the vite config. For global vitest settings like coverage, i do recommend using test in vite.config.ts to not have too many files.

A setup like this is going to be added to sv soonish and hopefully resolves all issues around this problem. Feedback welcome

mcous

mcous commented on Nov 25, 2024

@mcous
Collaborator

@dominikg that setup looks great! Let me know if there's anything on the testing-library side that could be updated to help the effort of getting this sort of thing into sv

@Stadly see Dominik's suggestion above; my comment about aliases predates Vitest's workspace configuration option. To throw my extra 2 cents into the mix, I also recommend that you explore structuring your application code such that auth and other framework concerns don't leak down to the level of component tests, and instead are tested at much higher levels, like E2E tests in playwright. You may be able to bypass this Vitest problem entirely with more separation between your own interesting logic that you want to test and framework stuff that you wire into

Stadly

Stadly commented on Nov 27, 2024

@Stadly

@dominikg that setup looks great! Let me know if there's anything on the testing-library side that could be updated to help the effort of getting this sort of thing into sv

@Stadly see Dominik's suggestion above; my comment about aliases predates Vitest's workspace configuration option. To throw my extra 2 cents into the mix, I also recommend that you explore structuring your application code such that auth and other framework concerns don't leak down to the level of component tests, and instead are tested at much higher levels, like E2E tests in playwright. You may be able to bypass this Vitest problem entirely with more separation between your own interesting logic that you want to test and framework stuff that you wire into

Thanks for the suggestion! I'm actually unit testing my handle hook using Vitest, so then the auth stuff is needed :)

Stadly

Stadly commented on Jan 21, 2025

@Stadly

for svelte5, i made an example using vitests workspace feature here: https://github.com/dominikg/vitest-example-svelte5

note that this uses vitest.workspace.ts instead of .config.ts and extends the vite config. For global vitest settings like coverage, i do recommend using test in vite.config.ts to not have too many files.

A setup like this is going to be added to sv soonish and hopefully resolves all issues around this problem. Feedback welcome

@dominikg It seems workspaces can now be defined without a separate file: vitest-dev/vitest#6923

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @yanick@dominikg@mcous@skokenes@Stadly

        Issue actions

          onMount isn't called when rendering components · Issue #222 · testing-library/svelte-testing-library