我正在尝试在带有Firebase的NodeJS上编写一些集成测试(firebase-admin,带有测试库jest和supertest),并且当我运行所有测试时,某些测试会随机失败。另外,我的测试通过了,但是好像正在运行太多测试用例时,某些api调用就会失败。这里有人已经有这样的问题了吗?这个问题有什么解决方案?是什么导致此问题? (注意:为了不混淆数据库的初始化,我按顺序运行测试。我将--runInBand选项与jest一起使用)
有一些可用的模拟库,但似乎它们可以与Firebase的旧api一起使用。
另一个解决方案是模拟操纵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
答案 0 :(得分:1)
我发现了问题:我在“ 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);
};
}