test: replace jest with vitest on backend (fehmer) (#5314)

* test: replace jest with vitest on backend

* fix
This commit is contained in:
Christian Fehmer 2024-04-17 13:39:10 +02:00 committed by GitHub
parent 208f47f455
commit 9bdbf5c595
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 2274 additions and 3226 deletions

View file

@ -1,9 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"Orta.vscode-jest",
"vitest.explorer",
"ryanluker.vscode-coverage-gutters",
"huntertran.auto-markdown-toc"
]
}

View file

@ -6,7 +6,7 @@ const mockApp = request(app);
describe("leaderboards controller test", () => {
it("GET /leaderboards/xp/weekly", async () => {
const configSpy = jest
const configSpy = vi
.spyOn(Configuration, "getCachedConfiguration")
.mockResolvedValue({
leaderboards: {

View file

@ -14,9 +14,9 @@ const mockDecodedToken: DecodedIdToken = {
iat: 0,
} as DecodedIdToken;
jest.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
vi.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
const resultMock = jest.spyOn(ResultDal, "getResults");
const resultMock = vi.spyOn(ResultDal, "getResults");
const mockApp = request(app);
@ -33,7 +33,7 @@ describe("result controller test", () => {
});
it("should get latest 1000 results for regular user", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
.get("/results")
@ -51,7 +51,7 @@ describe("result controller test", () => {
it("should get results filter by onOrAfterTimestamp", async () => {
//GIVEN
const now = Date.now();
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
.get("/results")
@ -70,7 +70,7 @@ describe("result controller test", () => {
});
it("should get with limit and offset", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
@ -89,7 +89,7 @@ describe("result controller test", () => {
});
it("should fail exceeding max limit for regular user", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
@ -112,7 +112,7 @@ describe("result controller test", () => {
});
it("should get with higher max limit for premium user", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
//WHEN
await mockApp
@ -132,7 +132,7 @@ describe("result controller test", () => {
});
it("should get results if offset/limit is partly outside the max limit", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
@ -152,7 +152,7 @@ describe("result controller test", () => {
});
it("should fail exceeding 1k limit", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
@ -171,7 +171,7 @@ describe("result controller test", () => {
});
it("should fail exceeding maxlimit for premium user", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
//WHEN
await mockApp
@ -194,7 +194,7 @@ describe("result controller test", () => {
});
it("should get results within regular limits for premium users even if premium is globally disabled", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
enablePremiumFeatures(false);
//WHEN
@ -214,7 +214,7 @@ describe("result controller test", () => {
});
it("should fail exceeding max limit for premium user if premium is globally disabled", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
enablePremiumFeatures(false);
//WHEN
@ -230,7 +230,7 @@ describe("result controller test", () => {
});
it("should get results with regular limit as default for premium users if premium is globally disabled", async () => {
//GIVEN
jest.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
enablePremiumFeatures(false);
//WHEN
@ -259,7 +259,7 @@ async function enablePremiumFeatures(premium: boolean): Promise<void> {
users: { premium: { enabled: premium } },
});
jest
.spyOn(Configuration, "getCachedConfiguration")
.mockResolvedValue(mockConfig);
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
);
}

View file

@ -21,7 +21,7 @@ describe("user controller test", () => {
captcha: "captcha",
};
jest.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue({
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue({
//if stuff breaks this might be the reason
users: {
signUp: true,
@ -90,7 +90,7 @@ describe("user controller test", () => {
})
.expect(409);
jest.restoreAllMocks();
vi.restoreAllMocks();
});
});
});

View file

@ -359,7 +359,7 @@ async function enablePremiumFeatures(premium: boolean): Promise<void> {
users: { premium: { enabled: premium } },
});
jest
.spyOn(Configuration, "getCachedConfiguration")
.mockResolvedValue(mockConfig);
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
);
}

View file

@ -30,7 +30,7 @@ async function createDummyData(
},
};
jest.spyOn(UserDal, "getUser").mockResolvedValue(dummyUser);
vi.spyOn(UserDal, "getUser").mockResolvedValue(dummyUser);
const tags: string[] = [];
if (tag !== undefined) tags.push(tag);
for (let i = 0; i < count; i++) {

View file

@ -1,5 +1,4 @@
import _ from "lodash";
import { ObjectId } from "mongodb";
import { updateStreak } from "../../src/dal/user";
import * as UserDAL from "../../src/dal/user";
@ -295,7 +294,7 @@ describe("UserDal", () => {
await UserDAL.addUser(testUser.name, testUser.email, testUser.uid);
// when
Date.now = jest.fn(() => 0);
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
@ -317,7 +316,7 @@ describe("UserDal", () => {
await UserDAL.addUser(testUser.name, testUser.email, testUser.uid);
// when
Date.now = jest.fn(() => 0);
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
// then
@ -337,11 +336,11 @@ describe("UserDal", () => {
await UserDAL.addUser(testUser.name, testUser.email, testUser.uid);
// when
Date.now = jest.fn(() => 0);
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
Date.now = jest.fn(() => 36000000);
Date.now = vi.fn(() => 36000000);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
@ -678,7 +677,7 @@ describe("UserDal", () => {
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = jest.fn(() => milis);
Date.now = vi.fn(() => milis);
const streak = await updateStreak("TestID", milis);
@ -736,7 +735,7 @@ describe("UserDal", () => {
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = jest.fn(() => milis);
Date.now = vi.fn(() => milis);
const streak = await updateStreak("TestID", milis);
@ -778,7 +777,7 @@ describe("UserDal", () => {
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = jest.fn(() => milis);
Date.now = vi.fn(() => milis);
const streak = await updateStreak("TestID", milis);

View file

@ -15,7 +15,7 @@ const mockDecodedToken: DecodedIdToken = {
iat: 0,
} as DecodedIdToken;
jest.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
vi.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
const mockApeKey = {
_id: new ObjectId(),
@ -28,9 +28,9 @@ const mockApeKey = {
useCount: 0,
enabled: true,
};
jest.spyOn(ApeKeys, "getApeKey").mockResolvedValue(mockApeKey);
jest.spyOn(ApeKeys, "updateLastUsedOn").mockResolvedValue();
const isDevModeMock = jest.spyOn(Misc, "isDevEnvironment");
vi.spyOn(ApeKeys, "getApeKey").mockResolvedValue(mockApeKey);
vi.spyOn(ApeKeys, "updateLastUsedOn").mockResolvedValue();
const isDevModeMock = vi.spyOn(Misc, "isDevEnvironment");
describe("middlewares/auth", () => {
let mockRequest: Partial<MonkeyTypes.Request>;
@ -60,9 +60,9 @@ describe("middlewares/auth", () => {
},
};
mockResponse = {
json: jest.fn(),
json: vi.fn(),
};
nextFunction = jest.fn((error) => {
nextFunction = vi.fn((error) => {
if (error) {
throw error;
}
@ -76,7 +76,7 @@ describe("middlewares/auth", () => {
describe("authenticateRequest", () => {
it("should fail if token is not fresh", async () => {
Date.now = jest.fn(() => 60001);
Date.now = vi.fn(() => 60001);
const authenticateRequest = Auth.authenticateRequest({
requireFreshToken: true,
@ -100,7 +100,7 @@ describe("middlewares/auth", () => {
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request if token is fresh", async () => {
Date.now = jest.fn(() => 10000);
Date.now = vi.fn(() => 10000);
const authenticateRequest = Auth.authenticateRequest({
requireFreshToken: true,

View file

@ -1,15 +1,17 @@
import { Collection, Db, MongoClient, WithId } from "mongodb";
import { afterAll, beforeAll, beforeEach, afterEach } from "vitest";
import * as MongoDbMock from "vitest-mongodb";
process.env["MODE"] = "dev";
jest.mock("../src/init/db", () => ({
vi.mock("../src/init/db", () => ({
__esModule: true,
getDb: (): Db => db,
collection: <T>(name: string): Collection<WithId<T>> =>
db.collection<WithId<T>>(name),
}));
jest.mock("../src/utils/logger", () => ({
vi.mock("../src/utils/logger", () => ({
__esModule: true,
default: {
error: console.error,
@ -20,7 +22,7 @@ jest.mock("../src/utils/logger", () => ({
},
}));
jest.mock("swagger-stats", () => ({
vi.mock("swagger-stats", () => ({
getMiddleware:
() =>
(_: unknown, __: unknown, next: () => unknown): void => {
@ -31,12 +33,11 @@ jest.mock("swagger-stats", () => ({
if (!process.env["REDIS_URI"]) {
// use mock if not set
process.env["REDIS_URI"] = "redis://mock";
jest.mock("ioredis", () => require("ioredis-mock"));
}
// TODO: better approach for this when needed
// https://firebase.google.com/docs/rules/unit-tests#run_local_unit_tests_with_the_version_9_javascript_sdk
jest.mock("firebase-admin", () => ({
vi.mock("firebase-admin", () => ({
__esModule: true,
default: {
auth: (): unknown => ({
@ -57,14 +58,21 @@ jest.mock("firebase-admin", () => ({
const collectionsForCleanUp = ["users"];
let db: Db;
let connection: MongoClient;
let client: MongoClient;
beforeAll(async () => {
connection = await MongoClient.connect(global.__MONGO_URI__);
db = connection.db();
await MongoDbMock.setup({
serverOptions: {
binary: {
version: "6.0.12",
},
},
});
client = new MongoClient(globalThis.__MONGO_URI__);
db = client.db();
});
beforeEach(async () => {
if (global.__MONGO_URI__) {
if (globalThis.__MONGO_URI__) {
await Promise.all(
collectionsForCleanUp.map((collection) =>
db.collection(collection).deleteMany({})
@ -80,5 +88,6 @@ afterEach(() => {
});
afterAll(async () => {
await connection.close();
await client?.close();
await MongoDbMock.teardown();
});

View file

@ -13,7 +13,8 @@
"esModuleInterop": true,
"strictNullChecks": true,
"skipLibCheck": true,
"noEmit": true
"noEmit": true,
"types": ["vitest/globals"]
},
"ts-node": {
"files": true

View file

@ -3,7 +3,7 @@ import * as misc from "../../src/utils/misc";
describe("Misc Utils", () => {
it("getCurrentDayTimestamp", () => {
Date.now = jest.fn(() => 1652743381);
Date.now = vi.fn(() => 1652743381);
const currentDay = misc.getCurrentDayTimestamp();
expect(currentDay).toBe(1641600000);
@ -313,7 +313,7 @@ describe("Misc Utils", () => {
});
it("getCurrentWeekTimestamp", () => {
Date.now = jest.fn(() => 825289481000); // Sun Feb 25 1996 23:04:41 GMT+0000
Date.now = vi.fn(() => 825289481000); // Sun Feb 25 1996 23:04:41 GMT+0000
const currentWeek = misc.getCurrentWeekTimestamp();
expect(currentWeek).toBe(824688000000); // Mon Feb 19 1996 00:00:00 GMT+0000
@ -417,7 +417,7 @@ describe("Misc Utils", () => {
];
testCases.forEach(({ now, input, offset, expected }) => {
Date.now = jest.fn(() => now);
Date.now = vi.fn(() => now);
expect(misc.isToday(input, offset)).toEqual(expected);
});
});
@ -481,7 +481,7 @@ describe("Misc Utils", () => {
];
testCases.forEach(({ now, input, offset, expected }) => {
Date.now = jest.fn(() => now);
Date.now = vi.fn(() => now);
expect(misc.isYesterday(input, offset)).toEqual(expected);
});
});

View file

@ -1,12 +0,0 @@
module.exports = {
mongodbMemoryServerOptions: {
binary: {
version: "6.0.12",
skipMD5: true,
},
instance: {
dbName: "jest",
},
autoStart: false,
},
};

View file

@ -1,18 +0,0 @@
import { defaults as tsjPreset } from "ts-jest/presets";
export default {
preset: "@shelf/jest-mongodb",
transform: tsjPreset.transform,
setupFilesAfterEnv: ["<rootDir>/__tests__/setup-tests.ts"],
modulePathIgnorePatterns: ["<rootDir>/__tests__/setup-tests.ts"],
moduleNameMapper: { "^uuid$": "uuid", "^msgpackr$": "msgpackr" },
coverageThreshold: {
global: {
// These percentages should never decrease
statements: 40,
branches: 37,
functions: 25,
lines: 43,
},
},
};

5315
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,8 @@
"watch": "tsc --build --watch",
"clean": "tsc --build --clean",
"start": "npm run build && node ./build/server.js",
"test": "jest --verbose --collect-coverage --runInBand",
"test": "vitest run",
"test-coverage": "vitest run --coverage",
"dev": "ts-node-dev --transpile-only --inspect -- ./src/server.ts",
"knip": "knip",
"docker-db-only": "docker compose -f docker/compose.db-only.yml up",
@ -52,13 +53,11 @@
"winston": "3.6.0"
},
"devDependencies": {
"@shelf/jest-mongodb": "4.2.0",
"@types/bcrypt": "5.0.0",
"@types/cors": "2.8.12",
"@types/cron": "1.7.3",
"@types/express": "4.17.21",
"@types/ioredis": "4.28.10",
"@types/jest": "27.5.0",
"@types/lodash": "4.14.178",
"@types/mustache": "4.2.2",
"@types/node": "18.19.1",
@ -71,12 +70,12 @@
"@types/swagger-ui-express": "4.1.3",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "8.3.4",
"@vitest/coverage-istanbul": "^1.5.0",
"ioredis-mock": "7.4.0",
"jest": "29.7.0",
"jest-environment-node": "29.7.0",
"supertest": "6.2.3",
"ts-jest": "29.1.2",
"ts-node-dev": "2.0.0",
"typescript": "5.3.3"
"typescript": "5.3.3",
"vitest": "^1.5.0",
"vitest-mongodb": "^0.0.5"
}
}

19
backend/vitest.config.js Normal file
View file

@ -0,0 +1,19 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
setupFiles: ["__tests__/setup-tests.ts"],
pool: "forks",
coverage: {
include: ["**/*.ts"],
provider: "istanbul",
reporter: [
"text", // For the terminal
"lcov", // For the VSCode extension and browser
],
},
},
});

View file

@ -34,10 +34,6 @@
"**/public/**": true,
"**/coverage/**": true
},
"jest.disabledWorkspaceFolders": ["root", "shared-types", "frontend"],
"vitest.enable": true,
"vitest.watchOnStartup": true,
"coverage-gutters.coverageBaseDir": "**/coverage",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSaveMode": "file",
"editor.formatOnSave": true,