W listopadize opublikowałem 5 wpisów :
RxJS :
Operator exhaustMap
NodeJS :
Jak testować MongoDB za pomocą MongoMemoryServer?
JavaScript :
Jak wypełnić tablicę danymi?
Jak pobrać niektóre elementy obiektu?
Narysowałem jeden komiks:
I am your father!
Przeczytałem dwie książki:
Ziarno i krew - Dariusz Rosiak
Chciwość - Marc Elsberg
Przesłuchałem jeden audiobook:
Przetaina - Andrzej Pilipiuk
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);
console .log(price);
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);
console .log(productPrice);
Pod tym linkiem dowiesz się jak pominąć niektóre właściwości obiektu
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);
Możemy też stworzyć tablicę obiektów:
const n = 3 ;
const array = Array (n).fill({ value : 0 });
console .log(array);
Tylko tutaj mamy mały problem:
array[1 ].value = 7 ;
console .log(array);
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);
array[1 ].value = 7 ;
console .log(array);
Sposób 3: Metoda map
Ale nie tak:
const n = 3 ;
const array = Array (n).map(() => ({ value : 0 }));
console .log(array);
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 )]
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);
array[1 ].value = 7 ;
console .log(array);
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:
Mongoose — Jest to biblioteka do obsługi MongoDB .
TypeScript — Podaję kod w TypeScript . Tutaj znajdziesz wpis jak podpiąć TypeScript w NodeJS
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ę:
connect — Łączymy się z bazą danych
close — Zamykamy połączenie z bazą
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 () => {
const ingredients: Ingredient[] = [
{
name: 'Test 1' ,
unit: 'g'
},
{
name: 'Test 2' ,
unit: 'l'
},
{
name: 'Test 3' ,
unit: 'tsp'
}
];
await IngredientModel.create(ingredients);
const fetchedIngredients: Ingredient[] = await IngredientModel.find({});
expect(fetchedIngredients.length).toEqual(3 );
});
test('should create ingredient' , async () => {
const ingredient: Ingredient = {
name: 'Test 1' ,
unit: 'g'
};
const createdIngredient: Ingredient = await IngredientModel.create(ingredient);
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
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.
Nowsze wpisy
Poprzednie wpisy