Как сделать интеграционные тесты NodeJS + Firebase Admin? - PullRequest
0 голосов
/ 19 сентября 2018

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

Есть несколько библиотек-макетов, но, похоже, они работают со старымиapi of 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:"sa@optimetriks.com",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.log(action+" "+obj+" in database");
        })
        .catch(() => {
          console.log("Error", "error while "+action+"ing "+obj+" to database");
        });
    } else {
      console.log(
        "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 --coverage --forceExit --runInBand --collectCoverageFrom=src/**/*ts

1 Ответ

0 голосов
/ 21 сентября 2018

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

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

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);
    };
  }
...