Skip to content

Commit

Permalink
13 set up unit and integration testing (#15)
Browse files Browse the repository at this point in the history
* 13 Added tests for login and register forms

* 13 Added apperror model test

* 13 Updated docker files

* 13 Fixed build failed

* 13 Fixed build failed

* 13 Fixed build failed

* 13 Added mocha types

* 13 Fixed build

* 13 Updated ci/cd

* 13 Updated ci/cd

* 13 Updated ci/cd

* 13 Updated ci/cd

* 13 Updated ci/cd

* 13 Updated ci/cd
  • Loading branch information
vladokuskov authored May 6, 2024
1 parent e00eb8f commit 2fb7ec3
Show file tree
Hide file tree
Showing 16 changed files with 663 additions and 100 deletions.
26 changes: 25 additions & 1 deletion .github/workflows/auto.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
name: Automated Testing and Verification 🤖

on:
workflow_dispatch:
push:
pull_request:
paths-ignore:
- 'README.md'

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build image 🛠
runs-on: ubuntu-latest
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ on:
workflow_dispatch:

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build and publish image 🛠️
runs-on: ubuntu-latest
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ on:
workflow_dispatch:

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build and publish image 🛠️
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions docker/production/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM node:21.5.0-alpine as base
RUN apk add --no-cache g++ make py3-pip libc6-compat
RUN apk add --no-cache git
WORKDIR /app
COPY package*.json ./
EXPOSE 8081
Expand All @@ -8,14 +9,13 @@ FROM base as builder
WORKDIR /app
COPY . .
RUN yarn install
RUN yarn lint
RUN yarn build

FROM base as production
WORKDIR /app

ENV NODE_ENV=production
RUN yarn install --frozen-lockfile
RUN yarn install

RUN addgroup -g 1001 -S nodejs
RUN adduser -S minddaily-frontend -u 1001
Expand Down
4 changes: 2 additions & 2 deletions docker/staging/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM node:21.5.0-alpine as base
RUN apk add --no-cache g++ make py3-pip libc6-compat
RUN apk add --no-cache git
WORKDIR /app
COPY package*.json ./
EXPOSE 8083
Expand All @@ -8,14 +9,13 @@ FROM base as builder
WORKDIR /app
COPY . .
RUN yarn install
RUN yarn lint
RUN yarn build

FROM base as stage
WORKDIR /app

ENV NODE_ENV=stage
RUN yarn install --frozen-lockfile
RUN yarn install

RUN addgroup -g 1001 -S nodejs
RUN adduser -S minddaily-frontend -u 1001
Expand Down
7 changes: 4 additions & 3 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const createJestConfig = nextJest({
// Add any custom config to be passed to Jest
const config: Config = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',

'^@/(.*)$': '<rootDir>/$1',
'^@/public/(.*)$': '<rootDir>/public/$1',
},
clearMocks: true,
collectCoverage: true,
passWithNoTests: true,
collectCoverageFrom: [
'./src/**/*.{js,jsx,ts,tsx}',
'!./src/**/_*.{js,jsx,ts,tsx}',
Expand All @@ -30,8 +30,9 @@ const config: Config = {
statements: 0,
},
},
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/tests/'],
testPathIgnorePatterns: ['<rootDir>/node_modules/'],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@
"url": "https://github.com/DreamUnit/minddaily-frontend"
},
"docs": {
"url": ""
"url": "https://minddaily-docs.vercel.app/"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"format": "prettier --check .",
"test": "jest",
"test:e2e": "playwright test",
"lint": "eslint ./"
},
"engines": {
"node": "21.5.0"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"@percy/playwright": "^1.0.5",
"@percy/sdk-utils": "^1.28.3",
"@tabler/icons-react": "^3.3.0",
"@testing-library/jest-dom": "^6.4.3",
"@testing-library/react": "^15.0.6",
"axios": "^1.6.7",
"daisyui": "^4.10.1",
"jest": "^29.7.0",
Expand All @@ -39,7 +39,8 @@
"zod": "^3.22.5"
},
"devDependencies": {
"@playwright/test": "^1.43.1",
"@types/jest": "^29.5.12",
"@types/mocha": "^10.0.6",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand All @@ -50,6 +51,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-tailwindcss": "^3.15.1",
"mocha": "^10.4.0",
"postcss": "^8",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/unit/AppError.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AppError } from '@/src/models/AppError';

describe('AppError', () => {
it('should create an instance with the provided message', () => {
const errorMessage = 'Test error message';
const error = new AppError(errorMessage);

expect(error instanceof AppError).toBe(true);
expect(error.message).toBe(errorMessage);
expect(error.name).toBe('AppError');
expect(error.cause).toBeUndefined();
});

it('should create an instance with the provided message and cause', () => {
const errorMessage = 'Test error message';
const cause = 'Test cause';
const error = new AppError(errorMessage, cause);

expect(error instanceof AppError).toBe(true);
expect(error.message).toBe(errorMessage);
expect(error.name).toBe('AppError');
expect(error.cause).toBe(cause);
});

it('should inherit from the Error class', () => {
const errorMessage = 'Test error message';
const error = new AppError(errorMessage);

expect(error instanceof Error).toBe(true);
expect(error.message).toBe(errorMessage);
});
});
83 changes: 83 additions & 0 deletions src/__tests__/unit/LoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import '@testing-library/jest-dom';

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import authService from '@/src/services/authService';
import { LoginForm } from '@/src/components/auth/LoginForm';
import { SocialAuthType } from '@/src/@types';

jest.mock('@/src/services/authService', () => ({
login: jest.fn(),
socialSignIn: jest.fn(),
}));

describe('LoginForm', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('renders login form correctly', () => {
render(<LoginForm />);
expect(screen.getByText('Sign in to MindDaily')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
expect(screen.getByText('Sign in')).toBeInTheDocument();
});

it('submits login form with valid credentials', async () => {
const email = '[email protected]';
const password = 'password';
(authService.login as jest.Mock).mockResolvedValueOnce({ email, password });

render(<LoginForm />);
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: email },
});
fireEvent.change(screen.getByPlaceholderText('Password'), {
target: { value: password },
});
fireEvent.click(screen.getByText('Sign in'));

await waitFor(() => {
expect(authService.login).toHaveBeenCalledTimes(1);
expect(authService.login).toHaveBeenCalledWith({ email, password });
});
});

it('displays error messages for invalid credentials', async () => {
const email = 'test';
const password = '123456_A';
(authService.login as jest.Mock).mockResolvedValueOnce({
email,
password,
});

render(<LoginForm />);
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: email },
});
fireEvent.change(screen.getByPlaceholderText('Password'), {
target: { value: password },
});
fireEvent.click(screen.getByText('Sign in'));

await waitFor(() => {
expect(authService.login).toHaveBeenCalledTimes(0);
});
});

it('submits social sign in with Google', async () => {
(authService.socialSignIn as jest.Mock).mockResolvedValueOnce(
SocialAuthType.Google
);

render(<LoginForm />);
fireEvent.click(screen.getByText('Continue with Google'));

await waitFor(() => {
expect(authService.socialSignIn).toHaveBeenCalledTimes(1);
expect(authService.socialSignIn).toHaveBeenCalledWith(
SocialAuthType.Google
);
});
});
});
Loading

0 comments on commit 2fb7ec3

Please sign in to comment.