Testování součástí ve Svelte

Testování nám pomáhá důvěřovat naší aplikaci a je to záchranná síť pro budoucí změny. V tomto tutoriálu nastavíme náš projekt Svelte tak, aby spouštěl testy našich komponent.

Zahájení nového projektu

Začněme vytvořením nového projektu:

pnpm dlx create-vite
// Project name: › testing-svelte
// Select a framework: › svelte
// Select a variant: › svelte-ts

cd testing-svelte
pnpm install

Existují i ​​jiné způsoby, jak vytvořit projekt Svelte, ale já dávám přednost použití Vite. Jedním z důvodů, proč preferuji používání Vite, je to, že jej bude používat i SvelteKit. Jsem také velkým fanouškem pnpm, ale můžete použít svého preferovaného správce balíčků. Ujistěte se, že při zakládání nového projektu pomocí npm postupujete podle Viteových dokumentů nebo yarn .

Instalace požadovaných závislostí

  • Jest:Tento rámec budu používat k testování. Je to ta, kterou znám nejlépe a cítím se s ní pohodlněji. Protože používám TypeScript, musím nainstalovat i jeho definice typů.
  • ts-jest:Transformátor pro práci se soubory TypeScript.
  • svelte-jester:předkompiluje komponenty Svelte před testy.
  • Testovací knihovna:Nezáleží na tom, jaký framework používám, budu hledat implementaci této oblíbené knihovny.
pnpm install --save-dev jest @types/jest @testing-library/svelte svelte-jester ts-jest

Konfigurace testů

Nyní, když jsou naše závislosti nainstalovány, musíme nakonfigurovat jest, aby připravil testy a spustil je.

Je vyžadováno několik kroků:

  • Převést *.ts soubory
  • Vyplňte *.svelte soubory
  • Spusťte testy

Vytvořte konfigurační soubor v kořenovém adresáři projektu:

// jest.config.js
export default {
  transform: {
    '^.+\\.ts$': 'ts-jest',
    '^.+\\.svelte$': [
      'svelte-jester',
      {
        preprocess: true,
      },
    ],
  },
  moduleFileExtensions: ['js', 'ts', 'svelte'],
};

Jest nyní bude používat ts-jest pro kompilaci *.ts soubory a svelte-jester pro *.svelte soubory.

Vytvoření nového testu

Pojďme otestovat komponentu Counter vytvořenou při spuštění projektu, ale nejprve zkontroluji, co naše komponenta dělá.

<script lang="ts">
  let count: number = 0;
  const increment = () => {
    count += 1;
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>

<style>
  button {
    font-family: inherit;
    font-size: inherit;
    padding: 1em 2em;
    color: #ff3e00;
    background-color: rgba(255, 62, 0, 0.1);
    border-radius: 2em;
    border: 2px solid rgba(255, 62, 0, 0);
    outline: none;
    width: 200px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
  }

  button:focus {
    border: 2px solid #ff3e00;
  }

  button:active {
    background-color: rgba(255, 62, 0, 0.2);
  }
</style>

Jedná se o velmi malou komponentu, kde tlačítko po kliknutí aktualizuje počet a tento počet se projeví v textu tlačítka.
Takže to je přesně to, co budeme testovat.

Vytvořím nový soubor ./lib/__tests__/Counter.spec.ts

/**
 * @jest-environment jsdom
 */

import { render, fireEvent } from '@testing-library/svelte';
import Counter from '../Counter.svelte';

describe('Counter', () => {
  it('it changes count when button is clicked', async () => {
    const { getByText } = render(Counter);
    const button = getByText(/Clicks:/);
    expect(button.innerHTML).toBe('Clicks: 0');
    await fireEvent.click(button);
    expect(button.innerHTML).toBe('Clicks: 1');
  });
});

Používáme render a fireEvent od testing-library . Mějte na paměti, že fireEvent vrací Promise a my musíme čekat aby se to splnilo.
Používám getByText dotaz, aby se na tlačítko kliklo.
Komentář v horní části vtipu informuje, že musíme použít jsdom jako prostředí. To udělá věci jako document k dispozici, jinak render nebude možné komponentu namontovat. To lze globálně nastavit v konfiguračním souboru.

Co kdybychom chtěli otestovat increment metoda v naší komponentě?
Pokud se nejedná o exportovanou funkci, doporučil bych ji otestovat prostřednictvím samotné renderované komponenty. V opačném případě je nejlepší možností extrahovat tuto funkci do jiného souboru a importovat ji do komponenty.

Podívejme se, jak to funguje.

// lib/increment.ts
export function increment (val: number) {
    val += 1;
    return val
  };
<!-- lib/Counter.svelte -->
<script lang="ts">
  import { increment } from './increment';
  let count: number = 0;
</script>

<button on:click={() => (count = increment(count))}>
  Clicks: {count}
</button>
<!-- ... -->

Naše předchozí testy budou stále fungovat a můžeme přidat test pro naši funkci.

// lib/__tests__/increment.spec.ts

import { increment } from '../increment';

describe('increment', () => {
  it('it returns value+1 to given value when called', async () => {
    expect(increment(0)).toBe(1);
    expect(increment(-1)).toBe(0);
    expect(increment(1.2)).toBe(2.2);
  });
});

V tomto testu není nutné používat jsdom jako testovací prostředí. Funkci právě testujeme.

Pokud byla naše metoda exportována, můžeme ji otestovat přímým přístupem.

<!-- lib/Counter.svelte -->
<script lang="ts">
  let count: number = 0;
  export const increment = () => {
    count += 1;
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>
<!-- ... -->
// lib/__tests__/Counter.spec.ts

describe('Counter Component', () => {
 // ... other tests

  describe('increment', () => {
    it('it exports a method', async () => {
      const { component } = render(Counter);
      expect(component.increment).toBeDefined();
    });

    it('it exports a method', async () => {
      const { getByText, component } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
      await component.increment()
      expect(button.innerHTML).toBe('Clicks: 1');
    });
  });
});

Když je metoda exportována, můžete k ní přistupovat přímo z vráceného component vlastnost render funkce.

POZNÁMKA:Pro zjednodušení nedoporučuji exportovat metody z komponenty, pokud nebyly určeny k exportu. Tím je zpřístupníte zvenčí a budete je moci volat z jiných komponent.

Události

Pokud vaše komponenta odešle událost, můžete ji otestovat pomocí component vlastnost vrácená render .

Abychom mohli odeslat událost, musíme importovat a zavolat createEventDispatcher a pak zavolejte vracející se funkci a dejte jí název události a volitelnou hodnotu.

<!-- lib/Counter.svelte -->
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  let count: number = 0;
  export const increment = () => {
    count += 1;
    dispatch('countChanged', count);
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>
<!-- ... -->
// lib/__tests__/Counter.spec.ts
// ...

  it('it emits an event', async () => {
    const { getByText, component } = render(Counter);
    const button = getByText(/Clicks:/);
    let mockEvent = jest.fn();
    component.$on('countChanged', function (event) {
      mockEvent(event.detail);
    });
    await fireEvent.click(button);

    // Some examples on what to test
    expect(mockEvent).toHaveBeenCalled(); // to check if it's been called
    expect(mockEvent).toHaveBeenCalledTimes(1); // to check how any times it's been called
    expect(mockEvent).toHaveBeenLastCalledWith(1); // to check the content of the event
    await fireEvent.click(button);
    expect(mockEvent).toHaveBeenCalledTimes(2);
    expect(mockEvent).toHaveBeenLastCalledWith(2);
  });

//...

Pro tento příklad jsem aktualizoval komponentu tak, aby generovala událost:countChanged . Pokaždé, když klepnete na tlačítko, událost vydá nový počet.
V testu používám getByText vyberte tlačítko, na které chcete kliknout, a component .

Pak používám component.$on(eventName) a zesměšňování funkce zpětného volání k testování emitované hodnoty (event.detail ).

Rekvizity

Můžete nastavit počáteční hodnoty props a upravit je pomocí rozhraní API komponent na straně klienta.

Pojďme aktualizovat naši komponentu, aby získala počáteční hodnotu počtu.

<!-- lib/Counter.svelte -->
<script lang="ts">
// ...
  export let count: number = 0;
// ...
</script>

<!-- ... -->

Převod count na vstupní hodnotu vyžaduje export deklarace proměnné.

Pak můžeme testovat:

  • výchozí hodnoty
  • počáteční hodnoty
  • aktualizace hodnot
// lib/__tests__/Counter.ts
// ...
describe('count', () => {
    it('defaults to 0', async () => {
      const { getByText } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
    });

    it('can have an initial value', async () => {
      const { getByText } = render(Counter, {props: {count: 33}});
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 33');
    });

    it('can be updated', async () => {
      const { getByText, component } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
      await component.$set({count: 41})
      expect(button.innerHTML).toBe('Clicks: 41');
    });
});
// ...

Druhý argument metody render používáme k předání počátečních hodnot do počítání a testujeme to pomocí tlačítka vykreslení

Pro aktualizaci hodnoty voláme $set metoda na component , který aktualizuje vykreslenou hodnotu při dalším zaškrtnutí. Proto musíme čekat to.

Shrnutí

Testování komponent pomocí Jest a Testing Library vám může pomoci vyhnout se chybám při vývoji a také vám může zvýšit jistotu při aplikování změn na existující kódovou základnu. Doufám, že tento blogový příspěvek je krokem vpřed k lepšímu testování.

Tyto příklady naleznete v tomto repozitáři

Toto Dot Labs je vývojová konzultační společnost zaměřená na poskytování augmentace zaměstnanců, architektonického poradenství a poradenství společnostem.

Pomáháme zavádět a vyučovat osvědčené postupy pro moderní web pomocí technologií, jako jsou React, Angular, Vue, Web Components, GraphQL, Node a další.