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