Search code examples
svelte-5

How to Unittest Svelte5 Effect Rune correctly


When writing a unittest for Svelte 5 Class/Function that uses an $effect rune (with vitest) I am currently doing the $effect() within a $effect.root(). Otherwise I would get the following error (If there is a better way please let me know):

svelte error: effect_orphan
`$effect` can only be used inside an effect (e.g. during component initialisation)
    at Module.effect_orphan

But this results in not destroying the $effect() at the end of an unittest. The following example demonstrates this as it outputs "T1" twice:

let cntr = $state(1);
describe('effect test', () => {
    test('test1', () => {
        $effect.root(() => {
            $effect(() => {
                console.log('T1', cntr);
            });
        });
        cntr += 1;
    });
    test('test2', () => {
        $effect.root(() => {
            $effect(() => {
                console.log('T2', cntr);
            });
        });
        cntr += 1;
    });
});

the output is:

T1 2
T2 3
T1 3      <- how can I avoid that T1 still exists (and outputs data)?

Solution

  • $effect.root returns a cleanup function. Call this at the end of the test.

    E.g.

    describe('effect test', () => {
        let cleanup;
    
        afterEach(() => cleanup?.());
    
        test('test1', () => {
            cleanup = $effect.root(() => {
                // ...
            });
    
            cntr += 1;
        });
    });
    

    Or on a single test:

    test('test1', context => {
        const cleanup = $effect.root(() => {
            // ...
        });
        context.onTestFinished(() => cleanup());
        cntr += 1;
    });
    

    (Using functions like afterEach and onTestFinished should ensure that the cleanup happens, even if an exception is thrown somewhere in the test.)