Как выполнить интеграционные тесты NodeJS + Firebase Admin?

Я пытаюсь написать несколько интеграционных тестов на NodeJS с Firebase (firebase-admin, с тестовой библиотекой jest и supertest), и некоторые тесты случайно не работают, когда я запускаю все свои тесты. Отдельно мои тесты проходят, но кажется, что когда выполняется слишком много тестовых примеров, некоторые вызовы api не работают. У кого-то здесь уже была такая проблема? Каковы решения этой проблемы? Что могло вызвать эту проблему? (NB: я запускаю свои тесты последовательно, чтобы не путать инициализацию моей базы данных. Я использую опцию --runInBand с шуткой)

Доступны некоторые имитирующие библиотеки, но похоже, что они работают со старым api firebase.

Другим решением было бы издеваться над всеми моими функциями, которые манипулируют firebase, но у меня больше не будет «настоящего» интеграционного теста, а это значит, что придется делать много дополнительного кода для написания этих макетов. Лучше ли это делать?

Заранее спасибо!


Обновлено: фрагмент кода:

initTest.js:

const request = require('supertest');
const net = require('net');
const app = require('../src/server').default;

export const initServer = () => {
    const server = net.createServer(function(sock) {
      sock.end('Hello world\n');
    });
    return server
}

export const createAdminAndReturnToken = async (password) => {
    await request(app.callback())
        .post('/admin/users/sa')
        .set('auth','')
        .send({password});
    // user logs in
    const res = await request(app.callback())
        .post('/web/users/login')
        .set('auth','')
        .send({email:"[email protected]",password})
    return res.body.data.token;
}

utils.ts:

import firestore from "../../src/tools/firestore/index";

export async function execOperations(operations,action,obj) {
    if (process.env.NODE_ENV === "test") {
      await Promise.all(operations)
        .then(() => {
          console.info(action+" "+obj+" in database");
        })
        .catch(() => {
          console.info("Error", "error while "+action+"ing "+obj+" to database");
        });
    } else {
      console.info(
        "Error",
        "cannot execute this action outside from the test environment"
      );
    }
  }

  //////////////////////// Delete collections ////////////////////////

export async function deleteAllCollections() {
    const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
    collections.forEach(collection => {
      deleteCollection(collection);
    });
  }

  export async function deleteCollection(collectionPath) {
    const batchSize = 10;
    var collectionRef = firestore.collection(collectionPath);
    var query = collectionRef.orderBy("__name__").limit(batchSize);

    return await new Promise((resolve, reject) => {
      deleteQueryBatch(firestore, query, batchSize, resolve, reject);
    });
  }

 async function deleteQueryBatch(firestore, query, batchSize, resolve, reject) {
    query
      .get()
      .then(snapshot => {
        // When there are no documents left, we are done
        if (snapshot.size == 0) {
          return 0;
        }

        // Delete documents in a batch
        var batch = firestore.batch();
        snapshot.docs.forEach(doc => {
          batch.delete(doc.ref);
        });

        return batch.commit().then(() => {
          return snapshot.size;
        });
      })
      .then(numDeleted => {
        if (numDeleted === 0) {
          resolve();
          return;
        }

        // Recurse on the next process tick, to avoid
        // exploding the stack.
        process.nextTick(() => {
          deleteQueryBatch(firestore, query, batchSize, resolve, reject);
        });
      })
      .catch(reject);
  }

populateClient.ts:

import firestore from "../../src/tools/firestore/index";
import {execOperations} from "./utils";
import { generateClientData } from "../factory/clientFactory";

jest.setTimeout(10000); // some actions here needs more than the standard 5s timeout of jest

// CLIENT
export async function addClient(client) {
    const clientData = await generateClientData(client);
    await firestore
        .collection("clients")
        .doc(clientData.id)
        .set(clientData)
}

export async function addClients(clientNb) {
  let operations = [];
  for (let i = 0; i < clientNb; i++) {
    const clientData = await generateClientData({});
    operations.push(
      await firestore
        .collection("clients")
        .doc(clientData.id)
        .set(clientData)
    );
  }
  await execOperations(operations,"add","client");
}

retrieveClient.ts:

import firestore from "../../src/tools/firestore/index";
import { resolveSnapshotData } from "../../src/tools/tools";

export async function getAllClients() {
    return new Promise((resolve, reject) => {
        firestore
          .collection("clients")
          .get()
          .then(data => {
            resolveSnapshotData(data, resolve);
          })
          .catch(err => reject(err));
      });
}

clients.test.js:

const request = require('supertest');
const app = require('../../../src/server').default;
const {deleteAllCollections, deleteCollection} = require('../../../__utils__/populate/utils')
const {addClient} = require('../../../__utils__/populate/populateClient')
const {getAllClients} = require('../../../__utils__/retrieve/retrieveClient')
const {initServer,createAdminAndReturnToken} = require('../../../__utils__/initTest');
const faker = require('faker');

let token_admin;
let _server;
// for simplicity, we use the same password for every users
const password = "secretpassword";

beforeAll(async () => {
    _server = initServer(); // start
    await deleteAllCollections()
    // create a super admin, login and store the token
    token_admin = await createAdminAndReturnToken(password);
    _server.close();   // stop
})

afterAll(async () => {
    // remove the users created during the campaign
    _server = initServer(); // start
    await deleteAllCollections()
    _server.close();   // stop
})

describe('Manage client', () => {

    beforeEach(() => {
        _server = initServer(); // start
    })

    afterEach(async () => {
        await deleteCollection("clients")
        _server.close();   // stop
    })

    describe('Get All clients', () => {

        const exec = (token) => {
            return request(app.callback())
            .get('/clients')
            .set('auth',token)
        }

        it('should return a 200 when super admin provide the action', async () => {
            const res = await exec(token_admin);
            expect(res.status).toBe(200);
        });

        it('should contain an empty array while no client registered', async () => {
            const res = await exec(token_admin);
            expect(res.body.data.clients).toEqual([]);
        });

        it('should contain an array with one item while a client is registered', async () => {
            // add a client
            const clientId = faker.random.uuid();
            await addClient({name:"client name",description:"client description",id:clientId})
            // call get clients and check the result
            const res = await exec(token_admin);
            expect(res.body.data.clients.length).toBe(1);
            expect(res.body.data.clients[0]).toHaveProperty('name','client name');
            expect(res.body.data.clients[0]).toHaveProperty('description','client description');
            expect(res.body.data.clients[0]).toHaveProperty('id',clientId);
        });
    })

    describe('Get client by ID', () => {

        const exec = (token,clientId) => {
            return request(app.callback())
            .get('/clients/' + clientId)
            .set('auth',token)
        }

        it('should return a 200 when super admin provide the action', async () => {
            const clientId = faker.random.uuid();
            await addClient({id:clientId})
            const res = await exec(token_admin,clientId);
            expect(res.status).toBe(200);
        });

        it('should return a 404 when the client does not exist', async () => {
            const nonExistingClientId = faker.random.uuid();
            const res = await exec(token_admin,nonExistingClientId);
            expect(res.status).toBe(404);
        });
    })

    describe('Update client', () => {

        const exec = (token,clientId,client) => {
            return request(app.callback())
            .patch('/clients/' + clientId)
            .set('auth',token)
            .send(client);
        }

        const clientModified = {
            name:"name modified",
            description:"description modified",
            app_user_licenses: 15
        }

        it('should return a 200 when super admin provide the action', async () => {
            const clientId = faker.random.uuid();
            await addClient({id:clientId})
            const res = await exec(token_admin,clientId,clientModified);
            expect(res.status).toBe(200);
            // check if the client id modified
            let clients = await getAllClients();
            expect(clients.length).toBe(1);
            expect(clients[0]).toHaveProperty('name',clientModified.name);
            expect(clients[0]).toHaveProperty('description',clientModified.description);
            expect(clients[0]).toHaveProperty('app_user_licenses',clientModified.app_user_licenses);
        });

        it('should return a 404 when the client does not exist', async () => {
            const nonExistingClientId = faker.random.uuid();
            const res = await exec(token_admin,nonExistingClientId,clientModified);
            expect(res.status).toBe(404);
        });
    })

    describe('Create client', () => {

        const exec = (token,client) => {
            return request(app.callback())
            .post('/clients')
            .set('auth',token)
            .send(client);
        }

        it('should return a 200 when super admin does the action', async () => {
            const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
            expect(res.status).toBe(200);
        });

        it('list of clients should be appended when a new client is created', async () => {
            let clients = await getAllClients();
            expect(clients.length).toBe(0);
            const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
            expect(res.status).toBe(200);
            clients = await getAllClients();
            expect(clients.length).toBe(1);
            expect(clients[0]).toHaveProperty('name','clientA');
            expect(clients[0]).toHaveProperty('description','description for clientA');
        });
    });

    describe('Delete client', () => {

        const exec = (token,clientId) => {
            return request(app.callback())
            .delete('/clients/'+ clientId)
            .set('auth',token);
        }

        it('should return a 200 when super admin does the action', async () => {
            const clientId = faker.random.uuid();
            await addClient({id:clientId})
            const res = await exec(token_admin,clientId);
            expect(res.status).toBe(200);
        });

        it('should return a 404 when trying to delete a non-existing id', async () => {
            const clientId = faker.random.uuid();
            const nonExistingId = faker.random.uuid();
            await addClient({id:clientId})
            const res = await exec(token_admin,nonExistingId);
            expect(res.status).toBe(404);
        });

        it('the client deleted should be removed from the list of clients', async () => {
            const clientIdToDelete = faker.random.uuid();
            const clientIdToRemain = faker.random.uuid();
            await addClient({id:clientIdToRemain})
            await addClient({id:clientIdToDelete})
            let clients = await getAllClients();
            expect(clients.length).toBe(2);
            await exec(token_admin,clientIdToDelete);
            clients = await getAllClients();
            expect(clients.length).toBe(1);
            expect(clients[0]).toHaveProperty('id',clientIdToRemain);
        });
    });
})

команда jest: jest --coverage --forceExit --runInBand --collectCoverageFrom=src/**/*ts

Нам нужно увидеть ваши тесты и любой соответствующий код. А также команды, которые вы используете для его запуска

Max Baldwin 19.09.2018 20:36

Я только что отредактировал это

Julien Mazars 20.09.2018 08:18

Не часть ответа, а пакет npm, который я бы порекомендовал вам. Вот как вы можете использовать его с веб-пакетом: npmjs.com/package/module-alias#usage-with-webpack

Max Baldwin 20.09.2018 16:18

Это вам не подходит? firebase.google.com/docs/functions/unit-testing

Max Baldwin 20.09.2018 16:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
1 244
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я обнаружил проблему: у меня возникла проблема с функцией "deleteAllCollection", я забыл поставить "ожидание".

Вот поправка для этой функции:

export async function deleteAllCollections() {
    const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
    for (const collection of collections) {
      await deleteCollection(collection);
    };
  }

Другие вопросы по теме