Alan Semenov Jest Mock XP

Vi holdt nylig en Meetup på kontoret vårt, hvor Alan Semenov, VP Engineering i Enonic, holdt en presentasjon om testing av Enonic-apper med Jest og Mock XP.

Testing your Enonic apps with Jest and Mock XP

Tidligere har vi fått tilbakemeldinger som antyder at vår tilnærming til testing av JavaScript-kode kan trenge forbedring. Vi har hørt dere, og som svar dykker vi ned i prosessen med å skrive effektive tester for Enonic-apper. Ukonvensjonelt nok starter vi med en FAQ-seksjon for å svare på vanlige spørsmål og legge et solid grunnlag før vi går inn i detaljene.

Hva er Jest?

Jest er et åpen kildekode testingsrammeverk laget med JavaScript for JavaScript. Selv om det hovedsakelig er designet for JavaScript-rammeverk, kan det også brukes med andre språk, forutsatt at de er JavaScript-baserte. Jest er foretrukket av flere grunner: 

  1. Enkel oppsett: I sin minimale versjon krever Jest ingen konfigurasjon, forutsatt at du skriver vanlig JavaScript. Tester må plasseres i spesifikt navngitte mapper og følge spesifikke navnekonvensjoner, men utover det kan du kjøre tester uten ekstra konfigurasjon.
  2. Integrasjon og støtte: Jest ble opprettet av Facebook, det samme teamet bak React. Denne native-integrasjonen med React sikrer langsiktig støtte. I tillegg støtter Jest TypeScript, noe som er avgjørende for utviklere som foretrekker TypeScript.
  3. Ytelse: Jest er veldig rask da det kjører tester parallelt.
  4. Innebygde funksjoner: Jest inkluderer mocking rett ut av boksen, og eliminerer behovet for å installere ekstra plugins for denne funksjonaliteten.
  5. Dokumentasjon: Jest er godt dokumentert og gir omfattende guider og eksempler.

Jest lar deg skrive tester for både klientside og serverside kode. Det støtter integrasjon med forskjellige rammeverk, inkludert React, Redux, Vue, Angular, Next.js og Nuxt.js. Støtten strekker seg utover disse, da det finnes tutorials, guider og kodeeksempler tilgjengelig for mange andre rammeverk.

Jest-oppsett

Å sette opp Jest er enkelt og fleksibelt, enten du bruker JavaScript eller TypeScript. 

For JavaScript, installerer du simpelthen Jest. Det legges til i din `package.json`, og du er klar til å starte. 

For TypeScript bruker du `ts-jest`, som inkluderer både Jest og de nødvendige typedefinisjonene. Det anbefales å bruke guiden for å generere konfigurasjonsfilen. Guiden vil stille spørsmål om ditt rammeverk og lagringsplass for tester, og deretter generere konfigurasjonsfilen for deg. Hvis du foretrekker å ikke bruke guiden, kan du manuelt konfigurere Jest ved å referere til dokumentasjonen for relevante konfigurasjonsegenskaper.

Deretter legger du til et skript i din `package.json` for å kjøre tester, vanligvis kalt "test." For hyppige testkjøringer, inkluder `--no-cache` for å sikre at tester alltid kjøres.

For å integrere Jest med Gradle i et XP-miljø, legg til en Gradle-oppgave som lenker til NPM-skriptet. Denne oppgaven kjører Jest-testene hver gang byggeprosessen utløses. Skriptet skal være `npm run test`. Hvis du ikke ønsker at tester skal kjøres ved hver bygging, kan du utelate denne linjen. Dette oppsettet sikrer at tester bare hoppes over hvis det ikke er noen endringer i enten testkoden eller kildekoden som testes.

Til slutt legger du til en konfigurasjon i ditt Gradle-oppsett for å sikre at Jest-tester inkluderes hver gang Gradles testoppgave kjøres. Dette sikrer omfattende testing ved å inkludere Jest-tester sammen med eventuelle andre tester du måtte kjøre, som Java-tester eller backend-tester.

Syntaks

Syntaksen til Jest er enkel og involverer tre nøkkelmetoder: `describe`, `test` og `expect`.

  • `describe` brukes til å gruppere relaterte tester.
  • `test` definerer et spesifikt brukstilfelle du ønsker å teste.
  • `expect` brukes til å evaluere et uttrykk og sammenligne resultatet med en matcher.

Matchers er metoder som lar deg påstå forskjellige forhold. Vanlige matchers inkluderer `toBe`, `toEqual`, `toBeTruthy`, `toBeFalsy`, `toBeNull`, og andre. Matchers kan også inverteres ved hjelp av `.not`. For eksempel, `expect(value).not.toEqual(something)` sjekker om verdien ikke er lik en spesifikk verdi.

Her er et enkelt eksempel for å illustrere syntaksen:

```javascript
describe('Basic Math Operations', () => {
  test('adds 2 + 2 to equal 4', () => {
    expect(2 + 2).toBe(4);
  });

  test('object assignment', () => {
    const data = { one: 1 };
    data['two'] = 2;
    expect(data).toEqual({ one: 1, two: 2 });
  });

  test('true is truthy', () => {
    expect(true).toBeTruthy();
  });

  test('false is falsy', () => {
    expect(false).toBeFalsy();
  });

  test('null is null', () => {
    expect(null).toBeNull();
  });

  test('value is not 5', () => {
    expect(4).not.toBe(5);
  });
});
```

I dette eksemplet:

  • `describe`-blokken grupperer tester relatert til grunnleggende matteoperasjoner.
  • `test`-blokkene definerer individuelle testtilfeller.
  • `expect`-funksjonen evaluerer uttrykk og sammenligner dem ved hjelp av matchers.

For å sammenligne objekter, bruk `toEqual` i stedet for `toBe`, da `toBe` er for primitivverdier. Matcherene `toBeTruthy` og `toBeFalsy` brukes for å teste boolske verdier.

Denne syntaksen gjør det enkelt å skrive og forstå tester, og sikrer at koden din oppfører seg som forventet.

Enkel test

For å starte med en enkel test, la oss ta et eksempel på en Fibonacci-sekvensgenerator. Vi vil sikre at den returnerer riktig sekvens for et gitt antall elementer. Her er hvordan du kan teste det i TypeScript ved hjelp av Jest:

  1. Importer de nødvendige metodene (`describe`, `expect`, `test`) fra Jest.
  2. Importer Fibonacci-sekvensgeneratorfunksjonen.
  3. Skriv en testtilfelle innenfor en `describe`-blokk for å verifisere de første ti tallene i sekvensen.

```typescript
import { describe, expect, test } from '@jest/globals';
import { generateFibonacci } from './path-to-your-code';

describe('Fibonacci Sequence Generator', () => {
  test('generates the first ten Fibonacci numbers', () => {
    expect(generateFibonacci(10)).toEqual([0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
  });
});
```

Denne enkle testen sjekker at funksjonen `generateFibonacci` korrekt returnerer de første ti Fibonacci-tallene.

Mocking

La oss deretter diskutere mocking. Mocking er avgjørende for å isolere kodenheten som testes, og sikre at den ikke kaller eksterne avhengigheter. Dette er forskjellig fra "spying", som bare sjekker om en metode ble kalt uten å erstatte den.

Her er et eksempel på mocking av en funksjon:

  1. Anta at du har en TypeScript-fil som eksporterer en `sanitize`-metode fra et kjernebibliotek.
  2. Når du tester en funksjon som er avhengig av `sanitize`, ønsker du å mocke `sanitize` for å kontrollere dens oppførsel under testen.

Her er hvordan du kan mocke `sanitize`-metoden ved hjelp av Jest:

  1. Importer de nødvendige modulene.
  2. Mock biblioteket og den spesifikke metoden.
  3. Skriv testtilfellet ditt.

```typescript
import { describe, expect, test, jest } from '@jest/globals';
import { yourFunction } from './path-to-your-code';
import * as libCommon from 'libxpm-common';

jest.mock('libxpm-common', () => ({
  sanitize: jest.fn()
}));

describe('Your Function Tests', () => {
  test('uses the mocked sanitize method', () => {
    libCommon.sanitize.mockImplementation(() => 'mocked output');

    const result = yourFunction('input that requires sanitization');
   
    expect(result).toBe('expected result based on mocked sanitize');
    expect(libCommon.sanitize).toHaveBeenCalledWith('input that requires sanitization');
  });
});
```

I dette eksemplet:

  • Biblioteket `libxpm-common` er mocket, og `sanitize`-metoden er erstattet med en mock-funksjon.
  • Mock-funksjonens oppførsel er definert ved hjelp av `mockImplementation`.
  • Testen verifiserer at `yourFunction` produserer forventet resultat ved bruk av den mockede `sanitize`-metoden, og sjekker at `sanitize` ble kalt med de riktige argumentene.

Ved å mocke avhengigheter kan du fokusere på å teste koden din isolert uten å bekymre deg for den faktiske implementasjonen av eksterne metoder. Denne tilnærmingen sikrer at testene dine er pålitelige og ikke avhenger av oppførselen til eksterne biblioteker.

Mock XP

Trenger du å mocke hele Enonic XP API? Den gode nyheten er at du ikke trenger å gjøre dette manuelt. Vi har laget en NPM-modul kalt Mock XP, som forenkler prosessen.

Mock XP er designet for å hjelpe deg med å kjøre tester uten å trenge det faktiske XP-miljøet. Dette er spesielt nyttig for CI/CD-pipelines på plattformer som GitHub, hvor XP kanskje ikke kjører. Mock XP mocker for tiden ni kjerne-APIer og er tilgjengelig i versjon 1.0.

Her er hvordan du bruker Mock XP:

  1. Installasjon: Du installerer det som enhver annen NPM-modul.
  2. Bruk: Importer `libnode` og opprett en mocket XP-instans ved hjelp av `server`.

Her er et grunnleggende eksempel:

```typescript
import { libnode } from 'mock-xp';
import { server } from 'mock-xp';

const mockServer = new server();
const repo = mockServer.createRepo('test-repo');
const nodeInstance = libnode.forServer(mockServer);

// Nå kan du bruke metoder fra libnode, som connect, create, etc.
nodeInstance.connect();
```

Dette oppsettet lar deg bruke kjerne XP-metoder uten å ha XP kjørende. Det sikrer at testene dine fokuserer utelukkende på koden din.

Mock XP-eksempel

Her er et mer komplekst eksempel for å illustrere hvordan Mock XP kan brukes til å opprette og verifisere noder:

```typescript
import { libnode } from 'mock-xp';
import { server } from 'mock-xp';

const mockServer = new server();
const nodeInstance = libnode.forServer(mockServer);

const createdNode = nodeInstance.createNode({ name: 'example-node' });
const expectedNode = { name: 'example-node' };

expect(createdNode).toEqual(expectedNode);
```

I dette eksemplet:

  • En mock server-instans opprettes.
  • `libnode`-instansen er koblet til denne mock-serveren.
  • En node opprettes ved hjelp av `createNode`-metoden.
  • Den opprettede noden sammenlignes med den forventede noden for å verifisere testen.

På denne måten sikrer du at testene dine er isolert fra det faktiske XP-miljøet, slik at du kan fokusere på funksjonaliteten til koden din uten å bekymre deg for den underliggende XP-infrastrukturen. Dette er spesielt nyttig for å opprettholde en robust testpakke som kan kjøre i forskjellige miljøer, inkludert CI/CD-pipelines.

Klientside (React)

Når du arbeider med React for klientsidetesting, er det viktig å sette opp et miljø som etterligner nettleseren, inkludert objekter som vindu og dokument. Du må justere Jest-konfigurasjonen for å bruke riktig miljø, vanligvis Jest DOM for klientsidetesting, i motsetning til Node for serversidetesting.

Start med å installere React og de nødvendige pakkene. En enkel React-komponent, som en som rendrer "Hello World", kan fungere som et grunnleggende eksempel. Testoppsettet innebærer å importere komponenten og bruke React Test Renderer til å simulere rendering av komponenten uten en nettleser. Du kan deretter bruke Jest til å verifisere den rendrede utgangen, og sikre at den inneholder de forventede elementene og teksten.

Beste praksis

Under vår integrasjon med Jest etablerte vi flere beste praksiser:

  1. Separat testmappe: Hold tester i en egen katalog fra kildekoden. Dette forhindrer at TypeScript-tester blir pakket med produksjonsbygget, noe som kan komplisere byggeprosessen. Vi kalte testkatalogen vår 'Jest.'
  2. < strong>Mappestruktur: Oppretthold separate mapper for klientside- og serversidetester. Speil kildekodestrukturen innenfor testmappen for enkelt å finne tilsvarende tester.
  3. Navnekonvensjoner: Bruk et konsekvent navnemønster for testfiler, som `.test.js` eller `.spec.js`, for å hjelpe Jest med å identifisere testfiler riktig.
  4. Enkle, enkle ansvarstester: Unngå å skrive tester som dekker flere funksjoner. Hver test skal være enkel og fokusert på ett aspekt for å forbedre vedlikeholdbarhet og redusere risikoen for å bryte tester.

I tillegg introduserte vi en TypeScript Starter som forenkler prosessen med å kjøre Jest-tester. Denne starteren er et minimalistisk oppsett, uten tidligere eksempelkode, slik at du kan legge til din kode og tester som automatisk gjenkjennes og kjøres av Jest.

Kjøre tester uten XP

I denne demonstrasjonen viste Alan hvordan du kan kjøre tester uten å ha Enonic XP kjørende. Først vil vi bruke kommandoen `enonic create` for å opprette et nytt eksempelprosjekt. I dette eksempelet kalte vi det "eksempel Jest." Det spesifikke navnet er ikke avgjørende her. Vi vil bruke en ren sandkasse, og det er ikke nødvendig å starte denne sandkassen siden vi ikke kjører XP. 

For å illustrere navigerte Alan til prosjektkatalogen for å vise oss strukturen. Dette er en faktisk XP-app, ikke et mock-oppsett. Kildekoden inneholder klient- og servertester, med noen klientsidetester inkludert.

La oss nå kjøre testene ved hjelp av Gradle. Siden dette er første kjøring, vil det installeres noen nødvendige NPM-biblioteker. Når det er gjort, vil NPM-testene kjøre, og vise at alle testene består uten at XP kjører. Dette er fordelaktig fordi det eliminerer behovet for å starte en XP-instans, og sparer tid. I dette tilfellet tok testene 1,5 sekunder å fullføre. For sammenligning, i et større prosjekt med over tusen tester, tok det rundt fem sekunder, noe som viser effektiviteten til denne tilnærmingen.

Spørsmål og svar

Q: Kan du kjøre NPM-tester direkte?
A: Ja, du kan bruke `NPM run test`.

Q: Kan du kjøre tester med CLI?
A: Ja, du kan bruke kommandoen `enonic project test`.

Når det gjelder ytelsestesting, er det integrasjon med K6 for ytelsestesting, tradisjonelt brukt til dette formålet. Selv om ytelsestesting har vært en vurdering i flere år, har det ennå ikke blitt fullstendig integrert, men det står på listen. Du kan fortsatt bruke K6 i dine pipelines for nå.

Oppsummert, kan du effektivt kjøre tester uten XP, ved å utnytte verktøy som Gradle og NPM, og utforske ytelsestesting med K6 i dine arbeidsflyter.

Hva er Composable CMS?

Relaterte blogginnlegg

Få enda mer innsikt 🤓