Podsumowanie: Listopad 2022

Opublikowano: 30.11.2022 - tagi: Podsumowanie Listopad 2022 Blog

W listopadize opublikowałem 5 wpisów:


RxJS:

  1. Operator exhaustMap

NodeJS:

  1. Jak testować MongoDB za pomocą MongoMemoryServer?

JavaScript:

  1. Jak wypełnić tablicę danymi?
  2. Jak pobrać niektóre elementy obiektu?

Narysowałem jeden komiks:

  1. I am your father!

Przeczytałem dwie książki:

  1. Ziarno i krew - Dariusz Rosiak
  2. Chciwość - Marc Elsberg

Przesłuchałem jeden audiobook:

  1. Przetaina - Andrzej Pilipiuk

JavaScript: Jak pobrać niektóre elementy obiektu?

Opublikowano: 24.11.2022 - tagi: JavaScript Obiekt

Pobieranie danych z obiektu

Mamy prosty obiekt:

const product = {
	name: 'Some product',
	price: 100,
	currency: 'PLN',
	weight: 1.5,
	size: 'L'
}

I chcemy pobrać na przykład tylko elementy: name i price. Można to zrobić tak:

const { name, price } = product;
console.log(name); // Some product
console.log(price); // 100

A co jeśli potrzebujemy pobrać te same dane, ale chcemy użyć innych nazw na przykład: productName i productPrice ?

Wystarczy napisać:

const { name: productName, price: productPrice } = product;
console.log(productName); // Some product
console.log(productPrice); // 100

Pod tym linkiem dowiesz się jak pominąć niektóre właściwości obiektu


JavaScript: Jak wypełnić tablicę danymi?

Opublikowano: 19.11.2022 - tagi: JavaScript Tablica

Inicjacja tablicy

Potrzebujesz stworzyć tablicę wypełnioną danymi? Można to zrobić na kilka sposobów:

Sposób 1: Metoda fill

Klasa Array udostępnia nam metodę fill:

const n = 5;
const array = Array(n).fill(0);
console.log(array); // [0, 0, 0, 0, 0]

Możemy też stworzyć tablicę obiektów:

const n = 3;
const array = Array(n).fill({ value: 0 });
console.log(array); // [{ value: 0 }, { value: 0 }, { value: 0 }]

Tylko tutaj mamy mały problem:

array[1].value = 7;
console.log(array); // [{ value: 7 }, { value: 7 }, { value: 7 }]

Została zmieniona wartość drugiego elementu, ale jak widać druga linijka, pokazuje nam, że zmieniła się wartość dla wszystkich elementów tablicy!

Dlaczego tak się dzieje? Metoda fill tworzy jedną instancję obiektu i wypełnia nią tablicę n-razy.

Sposób 2: Metoda from

Powyższy problem możemy rozwiązać za pomocą metody from również z klasy Array:

const n = 3;
const array = Array.from(Array(n), () => ({ value: 0 }));
console.log(array); // [{ value: 0 }, { value: 0 }, { value: 0 }]

array[1].value = 7;

console.log(array); // [{ value: 0 }, { value: 7 }, { value: 0 }]

Sposób 3: Metoda map

Ale nie tak:

const n = 3;
const array = Array(n).map(() => ({ value: 0 }));
console.log(array); // [3 empty slots]

Hmm nie to chcieliśmy osiągnąć. Pusta tablica?!

Zapis:

Array(n)

Tworzy pustą tablicę. Druga rzecz to metoda map nie iteruje po pustej tablicy.

Ten problem możemy rozwiązać za pomocą operatora spread:

[...Array(3)] // [undefined, undefined, undefined]

Taki zapis stworzy tablicę wypełnioną n-razy za pomocą undefined

Ostateczny przykład:

const n = 3;
const array = [...Array(n)].map(() => ({ value: 0 }));
console.log(array); // [{ value: 0 }, { value: 0 }, { value: 0 }]

array[1].value = 7;

console.log(array); // [{ value: 0 }, { value: 7 }, { value: 0 }]

NodeJS: Jak testować MongoDB za pomocą MongoMemoryServer?

Opublikowano: 15.11.2022 - tagi: JavaScript TypeScript NodeJS MongoDB RAM Baza danych Testowanie Mongoose

Co to jest MongoMemoryServer?

Pisanie testów na backendzie często wiąże się z testowaniem rzeczy związanych z bazą danych. Jak to można stestować? Łączenie się z bazą danych to słabe rozwiązanie, ponieważ zajmuje to trochę czasu, więc testy byłyby wolne. Można użyć na przykład mocków. Osobiście jestem za tym, żeby mocków używać jak najmniej, ponieważ chciałbym widzieć, jak się wszystko spina razem — odzwierciedla to prawdziwe działanie skryptu. Dlatego, to co można jeszcze zrobić to przechowywać dane w pamięci.

Tym zajmuje się biblioteka MongoMemoryServer. Nietrudno się domyślić, że jest to biblioteka do obsługi bazy danych MongoDB.

W tym wpisie opiszę konfigurację i podam prosty przykład jej użycia.

Konfiguracja MongoMemoryServer

Zanim zaczniemy, w tym wpisie zakładam, że używamy:

  1. Mongoose — Jest to biblioteka do obsługi MongoDB.
  2. TypeScript — Podaję kod w TypeScript. Tutaj znajdziesz wpis jak podpiąć TypeScript w NodeJS
  3. Jest — Do obsługi testów używam frameworka Jest. Z tego wpisu dowiesz się jak skonfigurować Jest

Instalacja MongoMemoryServer

Wywołaj komendę:

npm i -D mongodb-memory-server

Plik konfiguracyjny

Tworzymy plik konfiguracyjny: db.ts:

import mongoose from "mongoose";
import { MongoMemoryServer } from "mongodb-memory-server";

let mongoServer: MongoMemoryServer;

const connect = async () => {
    mongoServer = await MongoMemoryServer.create();
    await mongoose.connect(mongoServer.getUri(), { useNewUrlParser: true,  useUnifiedTopology: true });
};

const close = async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
    await mongoose.disconnect();
    await mongoServer.stop();
};

const clear = async () => {
    const collections = mongoose.connection.collections;
    for (const key in collections) {
        await collections[key].deleteMany({});
    }
};

export default { connect, close, clear };

Mamy trzy funkcję:

  1. connect — Łączymy się z bazą danych
  2. close — Zamykamy połączenie z bazą
  3. clear — Czyścimy dane, w danej kolekcji

Mamy konfigurację czas na sprawdzenie, czy to działa.

Model danych

Stwórzmy model danych: ingredient.ts:

import { model, Schema } from 'mongoose';

export interface Ingredient {
    name: string;
    unit: string;
}

const IngredientSchema = new Schema<Ingredient>({
    name: { type: String, required: true },
    unit: { type: String, required: true }
});

export const IngredientModel = model('Ingredient', IngredientSchema);

Pisanie testów

Stwórzmy plik: ingredient.spec.ts:

import db from "./db";
import {Ingredient, IngredientModel} from "./ingredient";

beforeAll(async () => await db.connect());

afterEach(async () => await db.clear());

afterAll(async () => await db.close());

test('should fetch all ingredients', async () => {
    // given
    const ingredients: Ingredient[] = [
        {
            name: 'Test 1',
            unit: 'g'
        },
        {
            name: 'Test 2',
            unit: 'l'
        },
        {
            name: 'Test 3',
            unit: 'tsp'
        }
    ];
    await IngredientModel.create(ingredients);

    // when
    const fetchedIngredients: Ingredient[] = await IngredientModel.find({});

    // then
    expect(fetchedIngredients.length).toEqual(3);
});

test('should create ingredient', async () => {
    // given
    const ingredient: Ingredient = {
        name: 'Test 1',
        unit: 'g'
    };

    // when
    const createdIngredient: Ingredient = await IngredientModel.create(ingredient);

    // then
    expect(createdIngredient.name).toEqual(ingredient.name);
    expect(createdIngredient.unit).toEqual(ingredient.unit);
});

W beforeAll, afterAll nawiązujemy połączenie lub je zamykamy z bazą danych, a w afterEach czyścimy dane przed uruchomienie kolejnego testu.

Uruchomienie testów

W pliku package.json dodaj:

"scripts": {
	"test": "jest --runInBand --detectOpenHandles"
}

Uruchom następnie testy:

npm run test

RxJS: Operator exhaustMap

Opublikowano: 08.11.2022 - tagi: JavaScript RxJS Operator

Do czego służy exhaustMap?

Operator exhaustMap pobiera dane z podanego źródła typu Observable. Ignoruje odbiór kolejnych danych do momentu, gdy dane z aktualnego źródła zostaną pobrane.

Przykład użycia exhaustMap

import { fromEvent, of } from 'rxjs';
import { exhaustMap, delay, map } from 'rxjs/operators';

const click$ = fromEvent(document, 'click');

click$.pipe(
    exhaustMap((event) => of(event).pipe(
        map(event => ({
          x: event.clientX,
          y: event.clientY
        })),
        delay(1000)
    )),
).subscribe(console.log)

Kliknij szybko kilka razy gdziekolwiek na stronie. Zostanie odebrany tylko jeden sygnał. Dlaczego? Ponieważ tak jak napisałem wyżej exhaustMap czeka na zakończenie aktualnej emisji danych. Dopiero po odebraniu danych pobierze następne.

exhaustMap jest podobony do operatora concatMap. Podmień w tym przykładzie exhaustMap na concatMap, żeby zobaczyć różnicę między nimi.