Skip to content

Commit

Permalink
Merge branch 'frontend-unit-test' into n11-unit-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chiaryan committed Nov 3, 2024
2 parents 221d737 + 97d6b6c commit 75a5c35
Show file tree
Hide file tree
Showing 7 changed files with 3,593 additions and 269 deletions.
26 changes: 25 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
branches:
- main
- staging

workflow_dispatch:

jobs:
question-service-tests:
Expand Down Expand Up @@ -53,6 +53,30 @@ jobs:
cd ./apps/question-service
go test ./...
frontend-unit-tests:
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '22'

- name: Setup .env
run: |
cd ./apps/frontend
cp .env.example .env
- name: Install pnpm
run: npm i -g pnpm

- name: Install dependencies
run: |
cd ./apps/frontend
pnpm i
- name: Run tests
run: |
cd ./apps/frontend
pnpm test
test:
runs-on: ubuntu-latest

Expand Down
9 changes: 9 additions & 0 deletions apps/frontend/__tests__/Datetime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { formatTime } from "@/utils/DateTime"

describe("datetime module", () => {
it("formats a time correctly", () => {
expect(formatTime(10)).toBe("00:10")
});
})


34 changes: 34 additions & 0 deletions apps/frontend/__tests__/dependencymocking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { GetQuestions } from "@/app/services/question"
import { getToken } from "@/app/services/login-store";

const TOKEN = 'mocktoken';

jest.mock("@/app/services/login-store", () => {
return {
__esModule: true,
getToken: jest.fn(() => TOKEN)
};
})

beforeEach(() => {
global.fetch = jest.fn().mockResolvedValue({
async json() {
return {}
}
});
})

describe("mock", () => {

it("mocks correctly", async () => {
await GetQuestions()
expect(jest.mocked(getToken).mock.calls).toEqual([[]])
expect(jest.mocked(fetch).mock.calls[0][1]).toEqual({
"headers": {
"Authorization": `Bearer ${TOKEN}`,
},
"method": "GET",
})
});

})
310 changes: 310 additions & 0 deletions apps/frontend/__tests__/question.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
import { CreateQuestion, DeleteQuestion, GetQuestions, GetSingleQuestion, Question } from "@/app/services/question"

const NEXT_PUBLIC_QUESTION_SERVICE_URL = process.env.NEXT_PUBLIC_QUESTION_SERVICE_URL

const QUESTIONS = [
{
"id": 12345,
"docRefId": "asdfDocRef",
"title": "Asdf",
"description": "Asdf description",
"categories": ["Arrays", "Algorithms"],
"complexity": "hard"
},
{
"id": 12346,
"docRefId": "qwerDocRef",
"title": "Qwer",
"description": "Qwer description",
"categories": ["Strings", "Data Structures"],
"complexity": "medium"
},
{
"id": 12347,
"docRefId": "zxcvDocRef",
"title": "Zxcv",
"description": "Zxcv description",
"categories": ["Graphs", "Algorithms"],
"complexity": "easy"
},
{
"id": 12348,
"docRefId": "tyuiDocRef",
"title": "Tyui",
"description": "Tyui description",
"categories": ["Trees", "Recursion"],
"complexity": "hard"
},
{
"id": 12349,
"docRefId": "ghjkDocRef",
"title": "Ghjk",
"description": "Ghjk description",
"categories": ["Dynamic Programming", "Math"],
"complexity": "medium"
},
{
"id": 12350,
"docRefId": "bnmlDocRef",
"title": "Bnml",
"description": "Bnml description",
"categories": ["Sorting", "Searching"],
"complexity": "easy"
},
{
"id": 12351,
"docRefId": "poiuDocRef",
"title": "Poiu",
"description": "Poiu description",
"categories": ["Bit Manipulation", "Algorithms"],
"complexity": "hard"
},
{
"id": 12352,
"docRefId": "lkjhDocRef",
"title": "Lkjh",
"description": "Lkjh description",
"categories": ["Greedy", "Data Structures"],
"complexity": "medium"
},
{
"id": 12353,
"docRefId": "mnbvDocRef",
"title": "Mnbv",
"description": "Mnbv description",
"categories": ["Backtracking", "Recursion"],
"complexity": "easy"
},
{
"id": 12354,
"docRefId": "vcxzDocRef",
"title": "Vcxz",
"description": "Vcxz description",
"categories": ["Graphs", "Dynamic Programming"],
"complexity": "hard"
}
]

jest.mock("@/app/services/login-store", () => {
return {
__esModule: true,
getToken: jest.fn(() => TOKEN)
};
})

function createMockResponse(obj: any): Response {
// @ts-ignore don't need the whole response
return {
json: () => Promise.resolve(obj),
ok: true,
status: 200
}
}

const TOKEN = "mockjwttoken"

describe("GetQuestions", () => {
beforeEach(() => {
global.fetch = jest.fn().mockResolvedValue({
async json() {
return QUESTIONS
}
});
})

it("gets all questions on the first page with () call", async () => {

const res = await GetQuestions()

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
expect(res).toStrictEqual(QUESTIONS)

});

it("gets all questions on the 2nd page with (2) call", async () => {

const res = await GetQuestions(2)

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?offset=10`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
});

it("gets all questions on the 2nd page with (limit=3) call", async () => {

await GetQuestions(undefined, 3)

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?limit=3`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
});

it("gets all questions on the 2nd page with (limit=3) call", async () => {

await GetQuestions(undefined, undefined, "difficulty asc")

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?sortField=difficulty&sortValue=asc`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
});

it("gets all questions on the 2nd page with (limit=3) call", async () => {

await GetQuestions(undefined, undefined, undefined, ["easy", "hard"])

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?complexity=easy,hard`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
});

it("formats urls for categories", async () => {

await GetQuestions(undefined, undefined, undefined, undefined, ["CatA", "CatB"])

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?categories=CatA,CatB`,
{
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}
]])
});

it("formats url for title", async () => {

await GetQuestions(undefined, undefined, undefined, undefined, undefined, "The Title Name")

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?title=The%20Title%20Name`,
{
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}
]])
});
})


describe("GetSingleQuestion", () => {
const DOCREF = "mockdocref";
beforeEach(() => {
global.fetch = jest.fn().mockResolvedValue({
async json() {
return QUESTIONS[0]
}
});
});

it("gets a question by docref", async () => {
const res = await GetSingleQuestion(DOCREF);

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`, {
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "GET",
}]])
expect(res).toStrictEqual(QUESTIONS[0]);
})
})

describe("CreateQuestion", () => {
it("uploads a question", async () => {
// grabs a subset of QUESTIONS[0]
const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0])
const createdQuestion = QUESTIONS[0];

global.fetch = jest.fn().mockResolvedValue({
status: 200,
statusText: "OK",
async json() {
return createdQuestion
}
});

const res = await CreateQuestion(newQuestion);

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${TOKEN}`,
},
method: "POST",
body: JSON.stringify(newQuestion)
}]])
expect(res).toStrictEqual(createdQuestion);
})

it("fails uploading question", async () => {
// grabs a subset of QUESTIONS[0]
const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0])

global.fetch = jest.fn().mockResolvedValue({
status: 400,
statusText: "Not Found",
data: "Question title already exists"
})

const res = CreateQuestion(newQuestion);

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${TOKEN}`,
},
method: "POST",
body: JSON.stringify(newQuestion)
}]])
await expect(res).rejects.toThrow("Error creating question: 400 Not Found")
})


})


describe("DeleteQuestion", () => {
const DOCREF = "mockdocref";
beforeEach(() => {
global.fetch = jest.fn().mockResolvedValue({
status: 200,
statusText: "OK",
data: `Question with ID ${DOCREF} deleted successfully`
});
});

it("deletes successfully", async () => {
const shouldbeNothing = await DeleteQuestion(DOCREF);

expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`,
{
headers: {
"Authorization": `Bearer ${TOKEN}`,
},
method: "DELETE",
}
]])
expect(shouldbeNothing).toBeUndefined();
})
})
Loading

0 comments on commit 75a5c35

Please sign in to comment.