/ Learning Log

Learning Log - Week #5

jest.useFakeTimers()

Żyjemy w świecie asynchronicznym. Tego nie da się ukryć. Nasze aplikacje bez asynchroniczności za dużo by nie zdziałały. Ale jak taką asnchroniczność testować? Z promisami jest spoko. Zwracamy w teście np. Promise.resolve() i jest poczeka na rozwiązanie promisa wykonując kod handlera, w którym wykonujemy aserscję:

it("test promise", () => {
    const promise = Promise.resolve();
    return promise.then(() => {
        // expect something here
    });
});

testowanie promise'a

No dobra...a co z setTimeout i setInterval? Jak testować takie rzeczy? Załóżmy, że mamy funckję, która jako argument przyjmuje callback, który ma być zawołany po pewnym czasie.

function doSomethingIn5s(callback) {
    setTimeout(callback, 5000);
}

Do tej pory do takich rzeczy używałem sinon.useFakeTimers.

  it("test callback", () => {
      const clock = sinon.useFakeTimers();
      const callback = sinon.stub();
      
      doSomething(callback);
      
      expect(callback).to.not.have.beenCalled();
      
      clock.tick(5001);
      
      expect(callback).to.have.beenCalled();
  });

Wszystko spoko, ale musimy znać czas po jakim zawołany będzie callback. W naszym przypadku jest to 5s.

Żeby nie exportować np. const CALLBACK_TIME = 5000 możemy użyć funkcji jest.runAllTimers().

it("test callback", () => {
      jest.useFakeTimers();
      const callback = jest.fn()
      
      doSomething(callback);
      
      expect(callback).to.not.have.beenCalled();
      
      jest.runAllTimers();
      
      expect(callback).to.have.beenCalled();
  });

Funkcja runAllTimers odpali nam wszystkie timery setTimeout czy setInterval czekając przy tym do następnego wywołania. Jedyny problem wystąpi wtedy, kiedy będziemy próbwać odpalić naszą funkcję rekurencyjnie. Ale na to też jest sposób...wystarczy użyć jest.runOnlyPendingTimers.

function doSomething(cb) {
    //do something
    setTimeout(() => {
        cb();
        setTimeout(() => doSomething(cb), 1000);
    }, 5000);
}

it("test", () => {
    jest.useFakeTimers();
    const callback = jest.fn()
      
    doSomething(callback);
      
    expect(callback).to.not.have.beenCalled();
      
    jest.runOnlyPendingTimers();
      
    expect(callback).to.have.beenCalled();
});

Jeśli chcemy ręcznie "przeskoczyć" daną wartość czasu możemy użyć jest.advanceTimersByTime(msToRun).

enyzme + React new context API

Znacie nowe ContextAPI? Jeśli nie - to wiele tracicie ;) zajrzyjcie na oficjalną stronę Reacta i poczytajcie sobie o tym. W tym tygodniu postanowiłem użyć nowego contextAPI i przy okazji przetestować jego użycie za pomocą biblioteki enzyme.

Załóżmy, że mamy komponent, który ma działać w pewnym kontekście.

import React from "react";

export const Context = React.createContext({});

const ParentComponent = ({ onSomething }) => (
  <Context.Provider value={{ onSomething }}>
     <OurComponent />
  </Context.Provider>
);

const OurComponent = () => (
  <Context.Consumer>
    {
      ({ onSomething }) => <Button onClick={ onSomething } />
    }
  </Context.Consumer>
);

trywialny przykład użycia React.createContext

Wiadomo ;) w takim prostym przypadku użycie contextu może być zbędne...ale na potrzeby demonstacji jest wystarczające.

Ok to co? ...testujemy enzymem:

import React from "react";
import { mount } from "enzyme";
import { OurComponent, Context } from "./ourModule";

it("test", () => {
    const wrapper = mount(
        <Context.Provider value={{ doSomething: jest.fn() }}>
            <OurComponent />
        </Context.Provider>
    );
    
    // Dalej nie ma co pisać bo dostajemy error
    // Error: Enzyme Internal Error: unknown node with tag 13
});

WTF? No i okazało się, że issue jest już zgłoszony i czekamy na fix: https://github.com/airbnb/enzyme/issues/1509.

Z jednej strony trochę lipa z workaround'em...ale z drugiej ten problem popchnął mnie tym bardziej w stronę porzucenia enzyme na rzecz chociażby react-testing-library...zobaczymy ;)

Parcel

W tym tygodniu zacząłem nowy side-project. Zmęczony trochę webpackiem postanowiłem sprawdzić inne rozwiązania. Wybór padł na parcel. I wiecie co? Chyba webpack pójdzie w odstawkę :) ...Dlaczego?

Parcel jest bundlerem. Tylko bundlerem i aż bundlerem. Zawiera wszystkie niezbędne loadery. Wiadomo, że aplikacja webowa nie może obejść się bez assetów typu html, css, font i js. Webpack do wszystkiego oprócz js'a potrzebuje dodatkowych loaderów...specyfikowania jakie pliki mają być przez dany loader przepuszczony itp. itd. Parcel ma te wszystkie loadery wbudowane i nie musimy martwić się o instalację dodatkowych pakietów. Wystarczy jedna komenda:

$ parcel index.html

Tak...index.html...plik który może wyglądać tak:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Egnyte Protect</title>
    <link rel="stylesheet" type="text/css" href="css/normalize.css">
</head>
<body>
    <div id="main"></div>
    <script type="application/javascript" src="javascript/main.js"></script>
</body>
</html>

Parcel sam ogarnie, że mamy dołączony plik normalize.css i main.js. Oba pliki zostaną wciągnięte do bundla :) - bez żadnej konfiguracji.

Co więcej? Parcel daje nam z automatu code splitting, bazujący na dynamicznych importach, hot module reloading czy common-chunks. Jeśli dodamy do tego prosty server z przeładowaniem na zmiany to dostajemy kompletne środowisko do budowania. I co najważniejsze - nie napisaliśmy przy tym ani jednej linijki konfiguracji. Używacie babela? Nie ma problemu! Parcel sam rozkmini, że macie w projekcie plik .babelrc i na jego podstawie odpowiednio przetransformuje wam Wasze pliki.

Twórcy biblioteki chwalą się, że jest szybsza od webpacka i browserify

Bundler Time
browserify       22.98s
webpack           20.71s
parcel 9.98s
parcel - with cache 2.64s

Czy to prawda ? ...powiem Wam jak już skończę cały projekt i odpalę bundla z wykorzystaniem Parcel'a i Webpack'a ;)

deno

2 i 3 czerwca w Berlinie, odbyła się konferencja JSconf EU 2018. Na oficjalny kanał youtube powoli trafiają zapisy video poszczególnych prezentacji. Ryan Dahl - twórca node.js - opowiadał o rzeczach, których żałuje właśnie odnośnie node.js. Bardzo ciekawa prezentacja - polecam.

Ryan bazując na swoich doświadczeniach napisał nową bibliotekę do odpalania javascript'u na V8. Przewrotnie nazwał ją deno ;) - przestawiając sylaby z node. I chociaż to dopiero prototyp to wygląda bardzo obiecująco. Wbudowany kompilator TypeScript, przełączniki ustawiające stopień dostępu do dysku czy sieci. Z ciekawością będę obserwował dalsze kroki deno ;)

reach router

Alternatywa dla react-router od jednego z jego autorów Ryan'a Florence'a. Sam Rayn pisze o reach-router tak:

To me, Reach Router is everything I missed about v3 and everything I love about v4, plus a few things I’ve always wanted a router in React to have, particularly focus management and relative links. I want a more accessible web, especially in React.

Jeszcze nie miałem okazji wypróbować reach-routera w akcji ale myślę, że przy okazji nowego projektu przy, którym zacząłem używać parcela, moim pierwszym wyborem odnośnie routingu będzie libka od Ryan'a ;)

Jeśli jesteście zainteresowani jak wygląda reach-router to odwiedźcie oficjalną stronę biblioteki. Dokumentacja jest ktrótka i bogata w przykłady - pozwólcie, że nie będę ich powielał na stronie ;)

https://reach.tech/

Jak już przetestuję reach-router to wrócę do Was z przemyśleniami - obiecuję ;)


To tyle w dzisiejszym odcinku learning loga.

Następny odcinek dopiero za 2 tygodnie - czas na ładowanie akumulatorów ;)

Pozdro!