Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mrc-6021 Vitest server pt 1 #234

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions app/server/tests/controllers/appsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { WodinWebError } from "../../src/errors/wodinWebError";

// Need to mock getSessionStore before importing the controller
let sessionIdFromFriendlyId: string | null;
const mockSessionStore = {
getSessionIdFromFriendlyId: jest.fn().mockImplementation(() => { return sessionIdFromFriendlyId; })
};
const mockGetSessionStore = jest.fn().mockReturnValue(mockSessionStore);
jest.mock("../../src/db/sessionStore", () => { return { getSessionStore: mockGetSessionStore }; });
const { mockSessionStore, mockGetSessionStore } = vi.hoisted(() => {
const mockSessionStore = {
getSessionIdFromFriendlyId: vi.fn().mockImplementation(() => { return sessionIdFromFriendlyId; })
};
const mockGetSessionStore = vi.fn().mockReturnValue(mockSessionStore);
return {
mockSessionStore,
mockGetSessionStore
}
})
vi.mock("../../src/db/sessionStore", () => { return { getSessionStore: mockGetSessionStore }; });
Comment on lines +6 to +16
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to explain this pattern in a couple of places in this PR and future ones:
vi.mock works almost the same as jest.mock however it cannot capture external variables except for those that have been hoisted through vi.hoisted so vi.hoisted expressions are always run first so are guaranteed to exist for the mock, so most of it is the same, just any variable (such as a mocked method that you want to test) that is used in the tests and the mock must be hoisted

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

umm are you sure?? ive never had to hoist like this when using vitest.. if your mock starts with mock it should auto hoist

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep i think so, here i have tried to just define them as variables with prefix mock however i get this error
image

its because vi.mock itself is hoisted to the top so i need to make sure those mocks are also hoisted so they exist when vi.mock runs


/* eslint-disable import/first */
import { AppsController } from "../../src/controllers/appsController";

describe("appsController", () => {
Expand All @@ -20,7 +25,7 @@ describe("appsController", () => {
wodinConfig = {}
) => {
const mockConfigReader = {
readConfigFile: jest.fn().mockReturnValue(appConfig)
readConfigFile: vi.fn().mockReturnValue(appConfig)
};
return {
app: {
Expand All @@ -45,8 +50,8 @@ describe("appsController", () => {
} as any;
};

const mockRender = jest.fn();
const mockStatus = jest.fn().mockReturnValue({ render: mockRender });
const mockRender = vi.fn();
const mockStatus = vi.fn().mockReturnValue({ render: mockRender });
const mockResponse = {
render: mockRender,
status: mockStatus
Expand All @@ -59,13 +64,13 @@ describe("appsController", () => {
});

afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});

it("renders view with app config", () => {
const appConfig = { title: "testTitle", appType: "testType" };
const request = getMockRequest(appConfig, "1234", undefined);
AppsController.getApp(request, mockResponse, jest.fn());
AppsController.getApp(request, mockResponse, vi.fn());

expect(mockRender).toBeCalledTimes(1);
expect(mockRender.mock.calls[0][0]).toBe("app");
Expand All @@ -90,7 +95,7 @@ describe("appsController", () => {

it("sets loadSessionId to be empty when not in query string", () => {
const request = getMockRequest({ title: "testTitle", appType: "testType" }, undefined, undefined);
AppsController.getApp(request, mockResponse, jest.fn());
AppsController.getApp(request, mockResponse, vi.fn());

expect(mockRender.mock.calls[0][1]).toStrictEqual({
appName: "test",
Expand All @@ -112,7 +117,7 @@ describe("appsController", () => {

it("gets session id from share parameter when provided", async () => {
const request = getMockRequest({ title: "testTitle", appType: "testType" }, undefined, "tiny-mouse");
await AppsController.getApp(request, mockResponse, jest.fn());
await AppsController.getApp(request, mockResponse, vi.fn());

expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(request);
Expand Down Expand Up @@ -140,7 +145,7 @@ describe("appsController", () => {
it("sets shareNotFound value when share parameter does not exist in db", async () => {
sessionIdFromFriendlyId = null;
const request = getMockRequest({ title: "testTitle", appType: "testType" }, undefined, "tiny-mouse");
await AppsController.getApp(request, mockResponse, jest.fn());
await AppsController.getApp(request, mockResponse, vi.fn());

expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockRender).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -171,7 +176,7 @@ describe("appsController", () => {
"app-not-found",
{ appName: "test" }
);
const next = jest.fn();
const next = vi.fn();
await AppsController.getApp(request, mockResponse, next);
expect(next).toHaveBeenCalledTimes(1);
expect(next.mock.calls[0][0]).toStrictEqual(expectedErr);
Expand All @@ -181,7 +186,7 @@ describe("appsController", () => {
it("removes trailing slash from baseUrl", async () => {
const wodinConfig = { baseUrl: "http://localhost:3000/instance/" };
const request = getMockRequest({ title: "testTitle", appType: "testType" }, "1234", undefined, wodinConfig);
await AppsController.getApp(request, mockResponse, jest.fn());
await AppsController.getApp(request, mockResponse, vi.fn());
expect(mockRender.mock.calls[0][1].baseUrl).toBe("http://localhost:3000/instance");
});
});
26 changes: 13 additions & 13 deletions app/server/tests/controllers/configController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ describe("configController", () => {
const defaultCode = ["default", "code"];
const appHelp = ["## HELP"];

const spyJsonResponseSuccess = jest.spyOn(jsonResponse, "jsonResponseSuccess");
const spyJsonResponseSuccess = vi.spyOn(jsonResponse, "jsonResponseSuccess");

beforeEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});

it("getConfig reads config file, default code file and app help file", () => {
const mockReadConfigFile = jest.fn().mockReturnValue(basicConfig);
const mockReadConfigFile = vi.fn().mockReturnValue(basicConfig);
const mockConfigReader = { readConfigFile: mockReadConfigFile } as any;
const mockReadDefaultCode = jest.fn().mockReturnValue(defaultCode);
const mockReadDefaultCode = vi.fn().mockReturnValue(defaultCode);
const mockDefaultCodeReader = { readFile: mockReadDefaultCode } as any;
const mockReadAppHelp = jest.fn().mockReturnValue(appHelp) as any;
const mockReadAppHelp = vi.fn().mockReturnValue(appHelp) as any;
const mockAppHelpReader = { readFile: mockReadAppHelp } as any;
const req = getRequest(mockConfigReader, mockDefaultCodeReader, mockAppHelpReader);

Expand Down Expand Up @@ -76,9 +76,9 @@ describe("configController", () => {
});

it("getConfig does not add help prop to config if no app help found", () => {
const mockConfigReader = { readConfigFile: jest.fn().mockReturnValue(basicConfig) } as any;
const mockDefaultCodeReader = { readFile: jest.fn().mockReturnValue([]) } as any;
const mockReadAppHelp = jest.fn().mockReturnValue([]) as any;
const mockConfigReader = { readConfigFile: vi.fn().mockReturnValue(basicConfig) } as any;
const mockDefaultCodeReader = { readFile: vi.fn().mockReturnValue([]) } as any;
const mockReadAppHelp = vi.fn().mockReturnValue([]) as any;
const mockAppHelpReader = { readFile: mockReadAppHelp } as any;
const req = getRequest(mockConfigReader, mockDefaultCodeReader, mockAppHelpReader);

Expand All @@ -96,15 +96,15 @@ describe("configController", () => {

it("getConfig includes help tabName from config file along with markdown", () => {
const mockConfigReader = {
readConfigFile: jest.fn().mockReturnValue({
readConfigFile: vi.fn().mockReturnValue({
...basicConfig,
help: {
tabName: "Help"
}
})
} as any;
const mockDefaultCodeReader = { readFile: jest.fn().mockReturnValue([]) } as any;
const mockReadAppHelp = jest.fn().mockReturnValue(appHelp) as any;
const mockDefaultCodeReader = { readFile: vi.fn().mockReturnValue([]) } as any;
const mockReadAppHelp = vi.fn().mockReturnValue(appHelp) as any;
const mockAppHelpReader = { readFile: mockReadAppHelp } as any;
const req = getRequest(mockConfigReader, mockDefaultCodeReader, mockAppHelpReader);

Expand All @@ -125,8 +125,8 @@ describe("configController", () => {
});

it("getConfig throws expected error when app config file is not found", () => {
const mockConfigReader = { readConfigFile: jest.fn().mockReturnValue(null) } as any;
const req = getRequest(mockConfigReader, jest.fn() as any, jest.fn() as any);
const mockConfigReader = { readConfigFile: vi.fn().mockReturnValue(null) } as any;
const req = getRequest(mockConfigReader, vi.fn() as any, vi.fn() as any);

const expectedError = new WodinError("App with name TestApp is not configured.", 404, ErrorType.NOT_FOUND);
expect(() => { ConfigController.getConfig(req, res); }).toThrow(expectedError);
Expand Down
2 changes: 1 addition & 1 deletion app/server/tests/controllers/indexController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("indexController", () => {
}
} as any;
const mockResponse = {
sendFile: jest.fn()
sendFile: vi.fn()
} as any;
IndexController.getIndex(mockRequest, mockResponse);
expect(mockResponse.sendFile).toBeCalledTimes(1);
Expand Down
11 changes: 5 additions & 6 deletions app/server/tests/controllers/odinController.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import * as apiService from "../../src/apiService";
import { OdinController } from "../../src/controllers/odinController";
import clearAllMocks = jest.clearAllMocks;

const mockAPIGet = jest.fn();
const mockAPIPost = jest.fn();
const mockAPIGet = vi.fn();
const mockAPIPost = vi.fn();
const mockAPIService = {
get: mockAPIGet,
post: mockAPIPost
} as any;
const mockNext = jest.fn();
const mockNext = vi.fn();

const apiSpy = jest.spyOn(apiService, "api").mockReturnValue(mockAPIService);
const apiSpy = vi.spyOn(apiService, "api").mockReturnValue(mockAPIService);

describe("odinController", () => {
const mockRequest = {
Expand All @@ -19,7 +18,7 @@ describe("odinController", () => {
const mockResponse = {} as any;

beforeEach(() => {
clearAllMocks();
vi.clearAllMocks();
});

it("getRunnerOde gets from api service", async () => {
Expand Down
68 changes: 36 additions & 32 deletions app/server/tests/controllers/sessionsController.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
const mockSessionStore = {
saveSession: jest.fn(),
getSessionsMetadata: jest.fn(),
saveSessionLabel: jest.fn(),
getSession: jest.fn(),
generateFriendlyId: jest.fn()
};

// Need to mock getSessionStore before importing the controller
const mockGetSessionStore = jest.fn().mockReturnValue(mockSessionStore);
jest.mock("../../src/db/sessionStore", () => { return { getSessionStore: mockGetSessionStore }; });

/* eslint-disable import/first */
const { mockSessionStore, mockGetSessionStore } = vi.hoisted(() => {
const mockSessionStore = {
saveSession: vi.fn(),
getSessionsMetadata: vi.fn(),
saveSessionLabel: vi.fn(),
getSession: vi.fn(),
generateFriendlyId: vi.fn()
};
// Need to mock getSessionStore before importing the controller
const mockGetSessionStore = vi.fn().mockReturnValue(mockSessionStore);
return {
mockSessionStore,
mockGetSessionStore
}
})
vi.mock("../../src/db/sessionStore", () => { return { getSessionStore: mockGetSessionStore }; });

import { serialiseSession, SessionsController } from "../../src/controllers/sessionsController";

describe("SessionsController", () => {
Expand All @@ -24,18 +28,18 @@ describe("SessionsController", () => {
} as any;

const res = {
end: jest.fn(),
header: jest.fn()
end: vi.fn(),
header: vi.fn()
} as any;

beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});

const testError = { message: "test error" };

it("can save session", async () => {
await SessionsController.postSession(req, res, jest.fn());
await SessionsController.postSession(req, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(req);
expect(mockSessionStore.saveSession).toHaveBeenCalledTimes(1);
Expand All @@ -46,7 +50,7 @@ describe("SessionsController", () => {

it("postSession handles error", async () => {
mockSessionStore.saveSession.mockImplementation(() => { throw testError; });
const next = jest.fn();
const next = vi.fn();
await SessionsController.postSession(req, res, next);
expect(next).toHaveBeenCalledWith(testError);
});
Expand All @@ -59,7 +63,7 @@ describe("SessionsController", () => {
removeDuplicates: "true"
}
};
await SessionsController.getSessionsMetadata(metadataReq, res, jest.fn());
await SessionsController.getSessionsMetadata(metadataReq, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(metadataReq);
expect(mockSessionStore.getSessionsMetadata).toHaveBeenCalledTimes(1);
Expand All @@ -78,7 +82,7 @@ describe("SessionsController", () => {
removeDuplicates: "false"
}
};
await SessionsController.getSessionsMetadata(metadataReq, res, jest.fn());
await SessionsController.getSessionsMetadata(metadataReq, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(metadataReq);
expect(mockSessionStore.getSessionsMetadata).toHaveBeenCalledTimes(1);
Expand All @@ -88,7 +92,7 @@ describe("SessionsController", () => {

it("getSessionMetadata handles error", async () => {
mockSessionStore.getSessionsMetadata.mockImplementation(() => { throw testError; });
const next = jest.fn();
const next = vi.fn();
const metadataReq = {
...req,
query: {
Expand All @@ -100,7 +104,7 @@ describe("SessionsController", () => {
});

it("can get empty session metadata with missing ids parameter", async () => {
await SessionsController.getSessionsMetadata(req, res, jest.fn());
await SessionsController.getSessionsMetadata(req, res, vi.fn());
expect(mockGetSessionStore).not.toHaveBeenCalled();
expect(res.header).toHaveBeenCalledWith("Content-Type", "application/json");
expect(res.end).toHaveBeenCalledTimes(1);
Expand All @@ -123,7 +127,7 @@ describe("SessionsController", () => {
body: "some label"
} as any;

SessionsController.postSessionLabel(labelReq, res, jest.fn());
SessionsController.postSessionLabel(labelReq, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(labelReq);
expect(mockSessionStore.saveSessionLabel).toHaveBeenCalledTimes(1);
Expand All @@ -133,7 +137,7 @@ describe("SessionsController", () => {

it("postSessionLabel handles error", async () => {
mockSessionStore.saveSessionLabel.mockImplementation(() => { throw testError; });
const next = jest.fn();
const next = vi.fn();
await SessionsController.postSessionLabel(req, res, next);
expect(next).toHaveBeenCalledWith(testError);
});
Expand All @@ -153,7 +157,7 @@ describe("SessionsController", () => {
id: "1234"
}
} as any;
SessionsController.getSession(sessionReq, res, jest.fn());
SessionsController.getSession(sessionReq, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(sessionReq);
expect(mockSessionStore.getSession).toHaveBeenCalledTimes(1);
Expand All @@ -162,7 +166,7 @@ describe("SessionsController", () => {

it("getSession handles error", async () => {
mockSessionStore.getSession.mockImplementation(() => { throw testError; });
const next = jest.fn();
const next = vi.fn();
await SessionsController.getSession(req, res, next);
expect(next).toHaveBeenCalledWith(testError);
});
Expand All @@ -182,7 +186,7 @@ describe("SessionsController", () => {
id: "1234"
}
} as any;
SessionsController.generateFriendlyId(sessionReq, res, jest.fn());
SessionsController.generateFriendlyId(sessionReq, res, vi.fn());
expect(mockGetSessionStore).toHaveBeenCalledTimes(1);
expect(mockGetSessionStore.mock.calls[0][0]).toBe(sessionReq);
expect(mockSessionStore.generateFriendlyId).toHaveBeenCalledTimes(1);
Expand All @@ -191,21 +195,21 @@ describe("SessionsController", () => {

it("generateFriendlyId handles error", async () => {
mockSessionStore.generateFriendlyId.mockImplementation(() => { throw testError; });
const next = jest.fn();
const next = vi.fn();
await SessionsController.generateFriendlyId(req, res, next);
expect(next).toHaveBeenCalledWith(testError);
});
});

describe("Sessions serialise correctly", () => {
const res = {
status: jest.fn(),
end: jest.fn(),
header: jest.fn()
status: vi.fn(),
end: vi.fn(),
header: vi.fn()
} as any;

beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});

it("serialises json string", () => {
Expand Down
Loading
Loading