przemuh.dev

Zatrzymaj czas z cy.clock

4/22/2020

Dzi艣 poka偶臋 Wam jak za pomoc膮 jednej komendy zatrzyma膰 czas. Niestety, tylko w testach napisanych w cypress.io. Je艣li znasz skuteczn膮 metod臋 na zatrzymanie czasu tak og贸lnie - w 偶yciu, to napisz do mnie. Ostatnio krucho u mnie z tym czasem, dlatego ka偶da wskaz贸wka jest na wag臋 z艂ota. Dobra 馃檪 koniec z tym 艣mieszkowaniem, bierzmy si臋 do roboty.

Opis testowanej aplikacji

Na pocz膮tek trzeba mie膰 co testowa膰. Nasza aplikacja b臋dzie do b贸lu prosta. Wy艣wietlamy czas wej艣cia na stron臋 oraz licznik, pokazuj膮cy ile czasu na niej sp臋dzili艣my.

Enter time:

Time on page: 0

import React from "react"

export default () => {
  const [enterDate, setEnterDate] = React.useState()
  const [counter, setCounter] = React.useState(0)

  React.useEffect(() => {
    setEnterDate(Date.now())

    const intervalId = setInterval(() => {
      setCounter(prev => prev + 1)
    }, 1000)

    return () => clearInterval(intervalId)
  }, [])

  return (
    <div>
      <p>
        Enter time: <span data-testid="enter-time">{enterDate}</span>
      </p>
      <p>
        Time on page: <span data-testid="counter">{counter}</span>
      </p>
    </div>
  )
}

Mo偶emy przej艣膰 teraz do napisania testu.

Testujemy!

Dla naszej aplikacji chcemy napisa膰 prosty test case sprawdzaj膮cy:

  • czy poprawnie wy艣wietlamy czas wej艣cia,
  • czy licznik zwi臋ksza swoj膮 warto艣膰 co sekund臋.

Spr贸bujmy w ten spos贸b:

cy.visit("/")
  .get("[data-testid=enter-time]")
  .should("have.text", Date.now().toString())

Na pierwszy rzut oka kod testu wygl膮da w miar臋 ok. Niestety nie przechodzi on weryfikacji 馃槩

B艂膮d przy pierwszej iteracji testu
B艂膮d przy pierwszej iteracji testu

Gdyby艣my zamiast liczby sprawdzali sformatowan膮 dat臋 np. 22-04-2020 pewnie nie by艂oby problemu. No ale nasz klient za偶yczy艂 sobie form臋 milisekund - #jak呕y膰? W takim wypadku sprawa si臋 nieco komplikuje.

Z pomoc膮 przychodzi nam komenda cy.clock. Nadpisuje ona globalne funkcje zwiazane z czasem i pozwala na kontrolowanie ich poprzez np. cy.tick().

Funkcjonalno艣ci na kt贸re wp艂ywa cy.clock to:

  • setTimeout
  • clearTimeout
  • setInterval
  • clearInterval
  • Date

Wi臋cej informacji znajdziecie w oficjalnej dokumentacji cypress.io.

Spr贸bujmy teraz doda膰 cy.clock do naszego testu:

cy.clock()
  .visit("/")
  .get("[data-testid=enter-time]")
  .should("have.text", Date.now().toString())

Nadal dostajemy b艂膮d, ale kiedy wczytamy si臋 w jego tre艣膰 zauwa偶ymy r贸偶nic臋:

expected <span> to have text '1587547901669', but the text was '0'

O co chodzi z tym zerem? Poniewa偶 czas reprezentowany jako timestamp to liczba sekund od rozpocz臋cia epoki Unixa (unix epoch), dlatego warto艣膰 0 reprezentuje 1 stycznia 1970. Mo偶na by zapyta膰 co stanie si臋 w momencie przekr臋cenia licznika, czyli 19 Stycznia 2038 roku - ale to ju偶 rozkmina na inny wpis 馃檪.

Wywo艂anie komendy cy.clock() bez argument贸w ustawia czas dla naszej aplikacji na 1 Stycznia 1970. Dlatego po prawej stronie asercji otrzymali艣my 0. 呕eby to zmieni膰 mo偶emy poda膰 porz膮dan膮 warto艣膰 daty jak膮 chcemy ustawi膰:

const now = Date.now()
cy.clock(now)

Dzi臋ki temu by膰 mo偶e magicznym trafem uda nam si臋 zgra膰 z dat膮 ustawion膮 w linijce odpowiadaj膮cej za asercj臋 should("have.text", Date.now().toString());. To zale偶y jaki szybki mamy komputer 馃槃. 呕eby poprawnie napisa膰 test nale偶y pami臋ta膰 o tym, 偶e cy.clock nadpisuje czas w naszej aplikacji a nie w testach. Dlatego wywo艂anie Date.now() w momencie asercji powinni艣my zamieni膰 na warto艣膰 zapisan膮 wcze艣niej w clock.

const now = Date.now()
cy.clock(now)
  .visit("/")
  .get("[data-testid=enter-time]")
  .should("have.text", now.toString())

Test przechodzi - zawsze! - sukces!. Jest tylko jedna r贸偶nica w dzia艂aniu aplikacji. Przed wykorzystaniem cy.clock nasz licznik w testach zmienia艂 si臋 co sekund臋. Po wykorzystaniu cy.clock licznik zatrzyma艂 si臋 na 0. Akurat dla nas jest to porz膮dany efekt. Nie tylko ustawili艣my porz膮dan膮 dat臋 ale te偶 zatrzymali艣my czas.

Aby ruszy膰 czas o porz膮dan膮 warto艣膰 wykorzystamy komend臋 cy.tick. Jako argument przyjmuje ona ilo艣膰 milisekund, o kt贸re chcemy przesun膮膰 nasz czas.

const now = Date.now()
cy.clock(now)
  .visit("/")
  .get("[data-testid=enter-time]")
  .should("have.text", now.toString())
  .get("[data-testid=counter]")
  .should("have.text", "0")
  .tick(1000)
  .get("[data-testid=counter]")
  .should("have.text", "1")

Sukces 馃帀! Mamy test sprawdzaj膮cy czas wej艣cia oraz dzia艂anie licznika.

A co je艣li chcieliby艣my ustawi膰 tylko dat臋? 馃

Bardzo dobre pytanie. Czasami mo偶e jednak zdarzy膰 si臋 tak, 偶e chcemy wp艂yn膮膰 tylko na obiekt Date pozostawiaj膮c w spokoju setTimeout i inne funkcje zwi膮zane z czasem. W takim wypadku nale偶y do wywo艂ania cy.clock doda膰 drugi argument - tablic臋 funkcji czasu, kt贸re chcemy nadpisa膰

cy.clock(Date.UTC(2020, 3, 22), ["Date"])

W tym przypadku ustawiamy dat臋/godzin臋 na 22 kwietnia 2020 00:00 UTC (miesi膮ce w Date liczymy od 0, dlatego 3 to Kwiecie艅 馃檪). Przy takim wywo艂aniu nie wp艂ywamy na pozosta艂e funkcje czasu takie jak setTimeout itp.


To tyle na dzi艣. Uzbrojeni w t臋 wiedz臋 mo偶ecie teraz zatrzyma膰 czas w swoich testach 馃槈

Powodzenia!