test(frontend): add jest setup and integration tests for API client
This commit is contained in:
parent
c9747d3596
commit
d3d6ae2991
4 changed files with 137 additions and 1 deletions
18
frontend/jest.config.js
Normal file
18
frontend/jest.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const nextJest = require('next/jest')
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||
dir: './',
|
||||
})
|
||||
|
||||
// Add any custom config to be passed to Jest
|
||||
const customJestConfig = {
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
}
|
||||
|
||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
4
frontend/jest.setup.js
Normal file
4
frontend/jest.setup.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import '@testing-library/jest-dom'
|
||||
|
||||
// Mock fetch globally
|
||||
global.fetch = jest.fn()
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
|
|
@ -63,12 +64,18 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^9.39.1",
|
||||
"jest": "^30.2.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"ts-node": "^10.9.2",
|
||||
"tw-animate-css": "1.3.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
|
|
|||
107
frontend/src/lib/__tests__/api.test.ts
Normal file
107
frontend/src/lib/__tests__/api.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
let jobsApi: any
|
||||
let companiesApi: any
|
||||
let usersApi: any
|
||||
|
||||
// Mock environment variable
|
||||
const ORIGINAL_ENV = process.env
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
process.env = { ...process.env, NEXT_PUBLIC_API_URL: 'http://test-api.com/api/v1' }
|
||||
|
||||
// Re-require modules to pick up new env vars
|
||||
const api = require('../api')
|
||||
jobsApi = api.jobsApi
|
||||
companiesApi = api.companiesApi
|
||||
usersApi = api.usersApi
|
||||
|
||||
global.fetch = jest.fn()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.env = ORIGINAL_ENV
|
||||
})
|
||||
|
||||
describe('API Client', () => {
|
||||
describe('jobsApi', () => {
|
||||
it('should fetch jobs with correct parameters', async () => {
|
||||
const mockJobs = {
|
||||
data: [{ id: 1, title: 'Dev Job' }],
|
||||
pagination: { total: 1 }
|
||||
}
|
||||
; (global.fetch as jest.Mock).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockJobs,
|
||||
})
|
||||
|
||||
const response = await jobsApi.list({ page: 1, limit: 10, companyId: 5 })
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'http://test-api.com/api/v1/jobs?page=1&limit=10&companyId=5',
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
})
|
||||
)
|
||||
expect(response).toEqual(mockJobs)
|
||||
})
|
||||
|
||||
it('should handle API errors correctly', async () => {
|
||||
; (global.fetch as jest.Mock).mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: async () => 'Not Found',
|
||||
})
|
||||
|
||||
await expect(jobsApi.list()).rejects.toThrow('Not Found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('companiesApi', () => {
|
||||
it('should create company correctly', async () => {
|
||||
const mockCompany = { id: '123', name: 'Test Corp' }
|
||||
; (global.fetch as jest.Mock).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockCompany,
|
||||
})
|
||||
|
||||
const newCompany = { name: 'Test Corp', slug: 'test-corp' }
|
||||
await companiesApi.create(newCompany)
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'http://test-api.com/api/v1/companies',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify(newCompany)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('URL Construction', () => {
|
||||
it('should handle double /api/v1 correctly', async () => {
|
||||
; (global.fetch as jest.Mock).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ([]),
|
||||
})
|
||||
|
||||
// We need to import the raw apiRequest function to test this properly,
|
||||
// but since it's not exported, we simulate via an exported function
|
||||
await usersApi.list()
|
||||
|
||||
// The apiRequest logic handles URL cleaning.
|
||||
// Expected: base http://test-api.com/api/v1 + endpoint /api/v1/users -> http://test-api.com/api/v1/users
|
||||
// NOT http://test-api.com/api/v1/api/v1/users
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'http://test-api.com/api/v1/users',
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue