This course is now available in English.Click here to change the language.

c

React-sovellusten testaaminen

Reactilla tehtyjen frontendien testaamiseen on monia tapoja. Aloitetaan niihin tutustuminen nyt.

Testit tehdään samaan tapaan kuin edellisessä osassa eli Facebookin Jest-kirjastolla. Jest onkin valmiiksi konfiguroitu create-react-app:illa luotuihin projekteihin.

Tarvitsemme Jestin lisäksi testaamiseen apukirjaston, jonka avulla React-komponentteja voidaan renderöidä testejä varten. Tähän tarkoitukseen ehdottomasti paras vaihtoehto vielä viime syksyyn asti oli AirBnB:n kehittämä enzyme-kirjasto. Enzyme ei kuitenkaan tue kunnolla Reactin hookeja, joten käytämme Enzymen sijaan viime aikoina nopeasti suosiota kasvattanutta kirjastoa react-testing-library. Jestin ilmaisuvoimaa kannattaa myös laajentaa kirjastolla jest-dom.

Asennetaan kirjastot komennolla:

npm install --save-dev @testing-library/react @testing-library/jest-dom

Testataan aluksi muistiinpanon renderöivää komponenttia:

const Note = ({ note, toggleImportance }) => {
  const label = note.important
    ? 'make not important'
    : 'make important'

  return (
    <li className='note'>      {note.content}
      <button onClick={toggleImportance}>{label}</button>
    </li>
  )
}

Huomaa, että muistiinpanon sisältävällä li-elementillä on CSS-luokka note, pääsemme sen avulla muistiinpanoon käsiksi testistä.

Komponentin renderöinti testiä varten

Tehdään testi tiedostoon src/components/Note.test.js, eli samaan hakemistoon, missä komponentti itsekin sijaitsee.

Ensimmäinen testi varmistaa, että komponentti renderöi muistiinpanon sisällön:

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, cleanup } from '@testing-library/react'
import Note from './Note'

afterEach(cleanup)

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const component = render(
    <Note note={note} />
  )

  expect(component.container).toHaveTextContent(
    'Component testing is done with react-testing-library'
  )
})

Alun konfiguroinnin jälkeen testi renderöi komponentin metodin react-testing-library-kirjaston tarjoaman render avulla:

const component = render(
  <Note note={note} />
)

Normaalisti React-komponentit renderöityvät DOM:iin. Nyt kuitenkin renderöimme komponentteja testeille sopivaan muotoon laittamatta niitä DOM:iin.

render palauttaa olion, jolla on useita kenttiä. Yksi kentistä on container, se sisältää koko komponentin renderöimän HTML:n.

Ekspektaatiossa varmistamme, että komponenttiin on renderöitynyt oikea teksti, eli muistiinpanon sisältö:

expect(component.container).toHaveTextContent(
  'Component testing is done with react-testing-library'
)

Testien suorittaminen

Create-react-app:issa on konfiguroitu testit oletusarvoisesti suoritettavaksi ns. watch-moodissa, eli jos suoritat testit komennolla npm test, jää konsoli odottamaan koodissa tapahtuvia muutoksia. Muutosten jälkeen testit suoritetaan automaattisesti ja Jest alkaa taas odottamaan uusia muutoksia koodiin.

Jos haluat ajaa testit "normaalisti", se onnistuu komennolla

CI=true npm test

HUOM: konsoli saattaa herjata virhettä, jos sinulla ei ole asennettuna watchmania. Watchman on Facebookin kehittämä tiedoston muutoksia tarkkaileva ohjelma. Ohjelma nopeuttaa testien ajoa ja ainakin osx sierrasta ylöspäin jatkuva testien vahtiminen aiheuttaa käyttäjillä virheilmoituksia. Näistä ilmoituksista pääsee eroon asentamalla Watchmanin.

Ohjeet ohjelman asentamiseen eri käyttöjärjestelmille löydät Watchmanin sivulta: https://facebook.github.io/watchman/

Testien sijainti

Reactissa on (ainakin) kaksi erilaista konventiota testien sijoittamiseen. Sijoitimme testit ehkä vallitsevan tavan mukaan, eli samaan hakemistoon missä testattava komponentti sijaitsee.

Toinen tapa olisi sijoittaa testit "normaaliin" tapaan omaan erilliseen hakemistoon. Valitaanpa kumpi tahansa tapa, on varmaa että se on jonkun mielestä täysin väärä.

Itse en pidä siitä, että testit ja normaali koodi ovat samassa hakemistossa. Noudatamme kuitenkin nyt tätä tapaa, sillä se on oletusarvo create-react-app:illa konfiguroiduissa sovelluksissa.

Sisällön etsiminen testattavasta komponentista

react-testing-library-kirjasto tarjoaa runsaasti tapoja, miten voimme tutkia testattavan komponentin sisältöä. Laajennetaan testiämme hiukan:

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const component = render(
    <Note note={note} />
  )

  // tapa 1
  expect(component.container).toHaveTextContent(
    'Component testing is done with react-testing-library'
  )

  // tapa 2
  const element = component.getByText(
    'Component testing is done with react-testing-library'
  )
  expect(element).toBeDefined()

  // tapa 3
  const div = component.container.querySelector('.note')
  expect(div).toHaveTextContent(
    'Component testing is done with react-testing-library'
  )
})

Ensimmäinen tapa siis etsii tiettyä tekstiä koko komponentin renderöimästä HTML-koodista.

Toisena käytimme render-metodin palauttamaan olioon liitettyä getByText-metodia, joka palauttaa sen elementin, jolla on määritelty teksti. Jos elementtiä ei ole, tapahtuu poikkeus. Eli mitään ekspektaatiota ei välttämättä edes tarvittaisi.

Kolmas tapa on etsiä komponentin sisältä tietty elementti metodilla querySelector, joka saa parametrikseen CSS-selektorin.

Testien debuggaaminen

Testejä tehdessä törmäämme tyypillisesti erittäin moniin ongelmiin.

Renderin palauttaman olion metodilla debug voimme tulostaa komponentin tuottaman HTML:n konsoliin, eli kun muutamme testiä seuraavasti,

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const component = render(
    <Note note={note} />
  )

  component.debug()
  // ...
})

konsoliin tulostuu komponentin generoima HTML:

console.log node_modules/@testing-library/react/dist/index.js:90
  <body>
    <div>
      <li
        class="note"
      >
        Component testing is done with react-testing-library
        <button>
          make not important
        </button>
      </li>
    </div>
  </body>

On myös mahdollista etsiä komponentista pienempi osa, ja tulostaa sen HTML-koodi, tällöin tarvitsemme metodia prettyDOM, joka löytyy react-testing-library:n mukana tulevasta kirjastosta @testing-library/dom:

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, cleanup } from '@testing-library/react'
import { prettyDOM } from '@testing-library/dom'import Note from './Note'

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const component = render(
    <Note note={note} />
  )
  const li = component.container.querySelector('li')
  
  console.log(prettyDOM(li))})

Eli haimme selektorin avulla komponentin sisältä li-elementin ja tulostimme sen HTML:n konsoliin:

console.log src/components/Note.test.js:21
  <li
    class="note"
  >
    Component testing is done with react-testing-library
    <button>
      make not important
    </button>
  </li>

setup

react-testing-library:n manuaali kehoittaa kutsumaan jokaisen testin jälkeen metodia cleanup. Hoidimme asian lisäämällä testitiedostoon afterEach-määreen, joka kutsuu metodia:

import React from 'react'
import '@testing-library/jest-dom/extend-expect'import { render, cleanup } from '@testing-library/react'
import { prettyDOM } from '@testing-library/dom' 
import Note from './Note'

afterEach(cleanup)

Voisimme toistaa saman kaikkiin testitiedostoihin. Parempi vaihtoehto on kuitenkin konfiguroida cleanup tapahtumaan automaattisesti. Tehdään konfiguraatiota varten tiedosto src/setupTests.js jolla on seuraava sisältö:

import '@testing-library/jest-dom/extend-expect'
import '@testing-library/react/cleanup-after-each'

Nyt pääsemme eroon molemmista ylläolevan testikoodin korostetuista riveistä.

HUOM mikäli testejä suoritettaessa ei löydetä tiedostossa src/setupTests.js tehtyjä konfiguraatioita, auttaa seuraavan asetuksen lisääminen tiedostoon package.json:

  "jest": {
    ...
    "setupFiles": [
      "<rootDir>/src/setupTests.js"
    ],
    ...
  }

Nappien painelu testeissä

Sisällön näyttämisen lisäksi toinen Note-komponenttien vastuulla oleva asia on huolehtia siitä, että painettaessa noten yhteydessä olevaa nappia, tulee propsina välitettyä tapahtumankäsittelijäfunktiota toggleImportance kutsua.

Testaus onnistuu seuraavasti:

import React from 'react'
import { render, fireEvent } from '@testing-library/react'import { prettyDOM } from '@testing-library/dom' 
import Note from './Note'

// ...

test('clicking the button calls event handler once', async () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const mockHandler = jest.fn()

  const { getByText } = render(
    <Note note={note} toggleImportance={mockHandler} />
  )

  const button = getByText('make not important')
  fireEvent.click(button)

  expect(mockHandler.mock.calls.length).toBe(1)
})

Testissä on muutama mielenkiintoinen seikka. Tapahtumankäsittelijäksi annetaan Jestin avulla määritelty mock-funktio:

const mockHandler = jest.fn()

Testi hakee renderöidystä komponentista napin tekstin perusteella ja klikkaa sitä:

const button = getByText('make not important')
fireEvent.click(button)

Klikkaaminen tapahtuu metodin fireEvent avulla.

Testin ekspektaatio varmistaa, että mock-funktiota on kutsuttu täsmälleen kerran:

expect(mockHandler.mock.calls.length).toBe(1)

Mockoliot ja -funktiot ovat testauksessa yleisesti käytettyjä valekomponentteja, joiden avulla korvataan testattavien komponenttien riippuvuuksia, eli niiden tarvitsemia muita komponentteja. Mockit mahdollistavat mm. kovakoodattujen syötteiden palauttamisen sekä niiden metodikutsujen lukumäärän sekä parametrien testauksen aikaisen tarkkailun.

Esimerkissämme mock-funktio sopi tarkoitukseen erinomaisesti, sillä sen avulla on helppo varmistaa, että metodia on kutsuttu täsmälleen kerran.

Komponentin Togglable testit

Tehdään komponentille Togglable muutama testi. Lisätään komponentin lapset renderöivään div-elementtiin CSS-luokka togglableContent:

const Togglable = React.forwardRef((props, ref) => {
  // ...

  return (
    <div>
      <div style={hideWhenVisible}>
        <button onClick={toggleVisibility}>
          {props.buttonLabel}
        </button>
      </div>
      <div style={showWhenVisible} className="togglableContent">        {props.children}
        <button onClick={toggleVisibility}>cancel</button>
      </div>
    </div>
  )
})

Testit ovat seuraavassa

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Togglable from './Togglable'

describe('<Togglable />', () => {
  let component

  beforeEach(() => {
    component = render(
      <Togglable buttonLabel="show...">
        <div className="testDiv" />
      </Togglable>
    )
  })

  test('renders its children', () => {
    component.container.querySelector('.testDiv')
  })

  test('at start the children are not displayed', () => {
    const div = component.container.querySelector('.togglableContent')

    expect(div).toHaveStyle('display: none')
  })

  test('after clicking the button, children are displayed', () => {
    const button = component.getByText('show...')
    fireEvent.click(button)

    const div = component.container.querySelector('.togglableContent')
    expect(div).not.toHaveStyle('display: none')
  })

})

Ennen jokaista testiä suoritettava beforeEach renderöi Togglable-komponentin muuttujaan component.

Ensimmäinen testi tarkastaa, että Togglable renderöi sen lapsikomponentin <div className="testDiv" />.

Loput testit varmistavat metodia toHaveStyle käyttäen, että Togglablen sisältämä lapsikomponentti on alussa näkymättömissä, eli sen sisältävään div-elementtiin liittyy tyyli { display: 'none' }, ja että nappia painettaessa komponentti näkyy, eli näkymättömäksi tekevää tyyliä ei enää ole.

Nappi etsitään jälleen nappiin liittyvän tekstin perusteella. Nappi oltaisiin voitu etsiä myös CSS-selektorin avulla

const button = component.container.querySelector('button')

Komponentissa on kaksi nappia, mutta koska querySelector palauttaa ensimmäisen löytyvän napin, löytyy napeista oikea.

Lisätään vielä mukaan testi, joka varmistaa että auki togglattu sisältö saadaan piilotettua painamalla komponentin toisena olevaa nappia

test('toggled content can be closed', () => {
  const button = component.container.querySelector('button')
  fireEvent.click(button)

  const closeButton = component.container.querySelector(
    'button:nth-child(2)'
  )
  fireEvent.click(closeButton)

  const div = component.container.querySelector('.togglableContent')
  expect(div).toHaveStyle('display: none')
})

eli määrittelimme selektorin, joka palauttaa toisena olevan napin button:nth-child(2). Testeissä ei kuitenkaan ole viisasta olla riippuvainen komponentin nappien järjestyksestä, joten parempi onkin hakea napit niiden tekstin perusteella:

test('toggled content can be closed', () => {
  const button = component.getByText('show...')
  fireEvent.click(button)

  const closeButton = component.getByText('cancel')
  fireEvent.click(closeButton)

  const div = component.container.querySelector('.togglableContent')
  expect(div).toHaveStyle('display: none')
})

Käyttämämme getByText on vain yksi monista queryistä, joita react-testing-library tarjoaa.

Sovelluksen tämänhetkinen koodi on kokonaisuudessaan githubissa, branchissa part5-7.

Lomakkeiden testaus

Käytimme jo edellisissä testeissä fireEvent-funktiota nappien klikkaamiseen:

const button = component.getByText('show...')
fireEvent.click(button)

Käytännössä siis loimme fireEventin avulla tapahtuman click nappia vastaavalle komponentille. Voimme myös simuloida lomakkeisiin kirjoittamista fireEventin avulla.

Tehdään testi komponentille NoteForm. Lomakkeen koodi näyttää seuraavalta

const NoteForm = ({ onSubmit, handleChange, value }) => {
  return (
    <div>
      <h2>Luo uusi muistiinpano</h2>

      <form onSubmit={onSubmit}>
        <input
          value={value}
          onChange={handleChange}
        />
        <button type="submit">save</button>
      </form>
    </div>
  )
}

Lomakkeen toimintaperiaatteena on synkronoida syötekentän tila sen ulkopuolella olevan React-komponentin tilaan. Lomakettamme on jossain määrin vaikea testata yksistään.

Teemmekin testejä varten apukomponentin Wrapper, joka renderöi NoteForm:in ja hallitsee lomakkeen tilaa parametrinaan saamansa propsin state avulla:

const Wrapper = (props) => {

  const onChange = (event) => {
    props.state.value = event.target.value
  }

  return (
    <NoteForm
      value={props.state.value}
      onSubmit={props.onSubmit}
      handleChange={onChange}
    />
  )
} 

Testi on seuraavassa:

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import NoteForm from './NoteForm'

const Wrapper = (props) => {
  // ...
}

test('<NoteForm /> updates parent state and calls onSubmit', () => {
  const onSubmit = jest.fn()
  const state = {
    value: ''
  }

  const component = render(
    <Wrapper onSubmit={onSubmit} state={state} />
  )

  const input = component.container.querySelector('input')
  const form = component.container.querySelector('form')

  fireEvent.change(input, { target: { value: 'testing of forms could be easier' } })
  fireEvent.submit(form)

  expect(onSubmit.mock.calls.length).toBe(1)
  expect(state.value).toBe('testing of forms could be easier')  
})

Testi luo Wrapper-komponentin, jolle se välittää propseina mockatun funktion onSubmit sekä tilaa edustavan olion state.

Wrapper välittää funktion edelleen NoteFormille tapahtuman onSubmit käsittelijäksi ja saamansa propsin state kentän value syötekentän input arvoksi.

Syötekenttään input kirjoittamista simuloidaan tekemällä syötekenttään tapahtuma change ja määrittelemällä sopiva olio, joka määrittelee syötekenttään 'kirjoitetun' sisällön.

Lomake lähetetään simuloimalla tapahtuma submit lomakkeelle.

Testin ensimmäinen ekspektaatio varmistaa, että lomakkeen lähetys on aikaansaanut tapahtumankäsittelijän kutsumisen. Toinen ekspektaatio tutkii komponentille Wrapper propsina välitettyä muuttujaa state, ja varmistaa, että lomakkeelle kirjoitettu teksti on siirtynyt tilaan.

Frontendin integraatiotestaus

Suoritimme edellisessä osassa backendille integraatiotestejä, jotka testasivat backendin tarjoaman API:n läpi backendia ja tietokantaa. Backendin testauksessa tehtiin tietoinen päätös olla kirjoittamatta yksikkötestejä sillä backendin koodi on melko suoraviivaista ja ongelmat tulevatkin esiin todennäköisemmin juuri monimutkaisemmissa skenaarioissa, joita integraatiotestit testaavat hyvin.

Toistaiseksi kaikki frontendiin tekemämme testit ovat olleet yksittäisten komponenttien oikeellisuutta valvovia yksikkötestejä. Yksikkötestaus on toki välillä hyödyllistä, mutta kattavinkaan yksikkötestaus ei riitä antamaan riittävää luotettavuutta sille, että järjestelmä toimii kokonaisuudessaan.

Tehdään nyt sovellukselle yksi integraatiotesti. Integraatiotestaus on huomattavasti komponenttien yksikkötestausta hankalampaa. Erityisesti sovelluksemme kohdalla ongelmia aiheuttaa kaksi seikkaa: sovellus hakee näytettävät muistiinpanot palvelimelta ja sovellus käyttää local storagea kirjautuneen käyttäjän tietojen tallettamiseen.

Local storage ei ole oletusarvoiseti käytettävissä testejä suorittaessa, sillä kyseessä on selaimen tarjoama toiminnallisuus ja testit ajetaan selaimen ulkopuolella. Ongelma on helppo korjata määrittelemällä testien suorituksen ajaksi mock joka matkii local storagea. Tapoja tähän on monia.

Koska testimme ei edellytä local storagelta juuri mitään toiminnallisuutta, teemme tiedostoon src/setupTests.js hyvin yksinkertaisen mockin

let savedItems = {}

const localStorageMock = {
  setItem: (key, item) => {
    savedItems[key] = item
  },
  getItem: (key) => savedItems[key],
  clear: savedItems = {}
}

Object.defineProperty(window, 'localStorage', { value: localStorageMock })

Toinen ongelmistamme on se, että sovellus hakee näytettävät muistiinpanot palvelimelta. Muistiinpanojen haku tapahtuu heti komponentin App luomisen jälkeen suoritettavassa effect hookissa:

const App = () => {
  // ...

  useEffect(() => {
    noteService
      .getAll().then(initialNotes => {
        setNotes(initialNotes)
      })
  }, [])

// ...
}

Jestin manual mock -konsepti tarjoaa tilanteeseen hyvän ratkaisun. Manual mockien avulla voidaan kokonainen moduuli, tässä tapauksessa noteService korvata testien ajaksi vaihtoehtoisella esim. kovakoodattua dataa tarjoavalla toiminnallisuudella.

Luodaan Jestin ohjeiden mukaisesti hakemistoon src/services alihakemisto __mocks__ (alussa ja lopussa kaksi alaviivaa) ja sinne tiedosto notes.js jonka määrittelemä metodi getAll palauttaa kovakoodatun listan muistiinpanoja:

const notes = [
  {
    id: '5a451df7571c224a31b5c8ce',
    content: 'HTML is easy',
    date: '2019-06-11T16:38:15.541Z',
    important: false,
    user: {
      _id: '5a437a9e514ab7f168ddf138',
      username: 'mluukkai',
      name: 'Matti Luukkainen'
    }
  },
  {
    id: '5a451e21e0b8b04a45638211',
    content: 'Browser can execute only javascript',
    date: '2019-06-11T16:38:57.694Z',
    important: true,
    user: {
      _id: '5a437a9e514ab7f168ddf138',
      username: 'mluukkai',
      name: 'Matti Luukkainen'
    }
  },
  {
    id: '5a451e30b5ffd44a58fa79ab',
    content: 'The most important methods of HTTP are GET and POST',
    date: '2019-06-11T16:39:12.713Z',
    important: true,
    user: {
      _id: '5a437a9e514ab7f168ddf138',
      username: 'mluukkai',
      name: 'Matti Luukkainen'
    }
  }
]

const getAll = () => {
  return Promise.resolve(notes)
}

export default { getAll }

Määritelty metodi getAll palauttaa muistiinpanojen listan käärittynä promiseksi metodin Promise.resolve avulla sillä käytettäessä metodia, oletetaan sen paluuarvon olevan promise:

Olemme valmiina määrittelemään testin. Koska kyseessä on koko sovellusta koskeva testi, tehdään se tiedostoon App.test.js:

import React from 'react'
import { render,  waitForElement } from '@testing-library/react'
jest.mock('./services/notes')
import App from './App'

describe('<App />', () => {
  test('renders all notes it gets from backend', async () => {
    const component = render(
      <App />
    )
    component.rerender(<App />)
    await waitForElement(
      () => component.container.querySelector('.note')
    )

    const notes = component.container.querySelectorAll('.note')
    expect(notes.length).toBe(3) 

    expect(component.container).toHaveTextContent(
      'HTML is easy'
    )
    expect(component.container).toHaveTextContent(
      'Browser can execute only javascript'
    )
    expect(component.container).toHaveTextContent(
      'The most important methods of HTTP are GET and POST'
    )
  })
})

Komennolla jest.mock('./services/notes') otetaan juuri määritelty mock käyttöön. Loogisempi paikka komennolle olisi kenties testien määrittelyt tekevä tiedosto src/setupTests.js.

Testi aloittaa renderöimällä komponentin uudelleen component.rerender(<App />), näin varmistetaan että kaikki efektit suoritetaan. Voi kyllä olla, että komento ei ole enää tarpeen uusimpien Reactin versioiden kanssa.

Koska efektin käynnistämä muistiinpanojen haku palvelimelta on asynkroninen tapahtuma, varmistamme funktion waitForElement avulla, että App ehtii renderöidä muistiinpanot

await waitForElement(() => component.container.querySelector('.note'))

Tämän jälkeen teemme varsinaiset expektaatiot, eli varmistetaan että sovelluksessa on kolme CSS-luokalla note merkittyä elementtiä, ja että kaikkien muistiinpanojen sisältö on renderöity.

Testauskattavuus

Testauskattavuus saadaan helposti selville suorittamalla testit komennolla

CI=true npm test -- --coverage
fullstack content

Melko primitiivinen HTML-muotoinen raportti generoituu hakemistoon coverage/lcov-report. HTML-muotoinen raportti kertoo mm. yksittäisen komponenttien testaamattomat koodirivit:

fullstack content

Huomaamme, että parannettavaa jäi vielä runsaasti.

Warning testejä suoritettaessa

Jos käytössäsi on Reactin version 16.8.6 tai aiempi, saatat saada testien läpimenosta huolimatta seuraavan varoituksen

fullstack content

Kuten täällä, kerrotaan the varoitus on aiheeton. Varoituksen aiheuttama ongelma korjataan Reactin versiossa 16.9.0 joka julkaistaan kesän aikana.

VAroituksen saa vaiennettua lisäämällä seuraavan koodin tiedostoon src/setupTests.js

const originalError = console.error
beforeAll(() => {
  console.error = (...args) => {
    if (/Warning.*not wrapped in act/.test(args[0])) {
      return
    }
    originalError.call(console, ...args)
  }
})

afterAll(() => {
  console.error = originalError
}) 

Sovelluksen tämänhetkinen koodi on kokonaisuudessaan githubissa, branchissa part5-8.

Snapshot-testaus

Jest tarjoaa "perinteisen" testaustavan lisäksi aivan uudenlaisen tavan testaukseen, ns. snapshot-testauksen. Mielenkiintoista snapshot-testauksessa on se, että sovelluskehittäjän ei tarvitse itse määritellä ollenkaan testejä, snapshot-testauksen käyttöönotto riittää.

Periaatteena on verrata komponenttien määrittelemää HTML:ää aina koodin muutoksen jälkeen siihen, minkälaisen HTML:n komponentit määrittelivät ennen muutosta.

Jos snapshot-testi huomaa muutoksen komponenttien määrittelemässä HTML:ssä, voi kyseessä joko olla haluttu muutos tai vahingossa aiheutettu "bugi". Snapshot-testi huomauttaa sovelluskehittäjälle, jos komponentin määrittelemä HTML muuttuu. Sovelluskehittäjä kertoo muutosten yhteydessä, oliko muutos haluttu. Jos muutos tuli yllätyksenä, eli kyseessä oli bugi, sovelluskehittäjä huomaa sen snapshot-testauksen ansiosta nopeasti.

End to end -testaus

Olemme tehneet sekä backendille että frontendille hieman niitä kokonaisuutena testaavia integraatiotestejä. Eräs tärkeä testauksen kategoria on vielä käsittelemättä, järjestelmää kokonaisuutena testaavat "end to end" (eli E2E) -testit.

Web-sovellusten E2E-testaus tapahtuu simuloidun selaimen avulla esimerkiksi Selenium-kirjastoa käyttäen. Toinen vaihtoehto on käyttää ns. headless browseria eli selainta, jolla ei ole ollenkaan graafista käyttöliittymää. Esim. Chromea on mahdollista suorittaa Headless-moodissa.

E2E testit ovat potentiaalisesti kaikkein hyödyllisin testikategoria, sillä ne tutkivat järjestelmää saman rajapinnan kautta kuin todelliset käyttäjät.

E2E-testeihin liittyy myös ikäviä puolia. Niiden konfigurointi on haastavampaa kuin yksikkö- ja integraatiotestien. E2E-testit ovat tyypillisesti myös melko hitaita ja isommassa ohjelmistossa niiden suoritusaika voi helposti nousta minuutteihin, tai jopa tunteihin. Tämä on ikävää sovelluskehityksen kannalta, sillä sovellusta koodatessa on erittäin hyödyllistä pystyä ajamaan testejä mahdollisimman usein koodin regressioiden varalta.

Palaamme end to end -testeihin kurssin viimeisessä, eli seitsemännessä osassa.