Skip to content

Commit d4c5822

Browse files
committed
Add Jest for testing and create a health endpoint test
Integrated `jest` and `supertest` for testing purposes. Updated `test` script in `package.json` to use Jest. Added a basic test case for the `/health` endpoint to verify server health status.
1 parent 9bd61d5 commit d4c5822

7 files changed

Lines changed: 4375 additions & 390 deletions

File tree

__tests__/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Tests for Node.js Simple Notes App
2+
3+
This directory contains tests for the Node.js Simple Notes App.
4+
5+
## Test Structure
6+
7+
- `__tests__/models/` - Tests for data models
8+
- `__tests__/api/` - Tests for API endpoints
9+
- `__tests__/routes/` - Tests for route handlers
10+
11+
## Running Tests
12+
13+
To run all tests:
14+
15+
```bash
16+
npm test
17+
```
18+
19+
To run a specific test file:
20+
21+
```bash
22+
npm test -- __tests__/models/note.test.js
23+
```
24+
25+
## Test Coverage
26+
27+
The tests cover:
28+
29+
1. **Note Model**
30+
- Creating notes with default and custom values
31+
- Converting between Note objects and plain objects
32+
33+
2. **Health Endpoint**
34+
- Verifying the health endpoint returns the correct status
35+
36+
3. **Notes API Routes**
37+
- GET /api/notes - Retrieving all notes
38+
- POST /api/notes - Creating new notes
39+
- Validation of required fields
40+
41+
## Adding New Tests
42+
43+
When adding new tests:
44+
45+
1. Place them in the appropriate directory based on what they're testing
46+
2. Follow the existing patterns for mocking dependencies
47+
3. Ensure tests are isolated and don't depend on external services

__tests__/api/health.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import express from 'express';
2+
import request from 'supertest';
3+
4+
describe('Health Endpoint', () => {
5+
let app;
6+
7+
beforeEach(() => {
8+
app = express();
9+
10+
// Add the health endpoint to the app
11+
app.get('/health', (req, res) => {
12+
res.status(200).json({ status: 'ok' });
13+
});
14+
});
15+
16+
test('should return 200 OK with status ok', async () => {
17+
const response = await request(app).get('/health');
18+
19+
expect(response.status).toBe(200);
20+
expect(response.body).toEqual({ status: 'ok' });
21+
});
22+
});

__tests__/models/note.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Note } from '../../src/models/note.js';
2+
3+
describe('Note Model', () => {
4+
test('should create a note with default values', () => {
5+
const note = new Note();
6+
7+
expect(note.id).toBeNull();
8+
expect(note.title).toBe('');
9+
expect(note.content).toBe('');
10+
expect(note.createdAt).toBeInstanceOf(Date);
11+
expect(note.updatedAt).toBeInstanceOf(Date);
12+
});
13+
14+
test('should create a note with provided values', () => {
15+
const id = '123';
16+
const title = 'Test Note';
17+
const content = 'This is a test note';
18+
const createdAt = new Date('2023-01-01');
19+
const updatedAt = new Date('2023-01-02');
20+
21+
const note = new Note(id, title, content, createdAt, updatedAt);
22+
23+
expect(note.id).toBe(id);
24+
expect(note.title).toBe(title);
25+
expect(note.content).toBe(content);
26+
expect(note.createdAt).toBe(createdAt);
27+
expect(note.updatedAt).toBe(updatedAt);
28+
});
29+
30+
test('should create a note from an object', () => {
31+
const obj = {
32+
id: '456',
33+
title: 'From Object',
34+
content: 'Created from an object',
35+
createdAt: '2023-02-01T00:00:00.000Z',
36+
updatedAt: '2023-02-02T00:00:00.000Z'
37+
};
38+
39+
const note = Note.fromObject(obj);
40+
41+
expect(note).toBeInstanceOf(Note);
42+
expect(note.id).toBe(obj.id);
43+
expect(note.title).toBe(obj.title);
44+
expect(note.content).toBe(obj.content);
45+
expect(note.createdAt).toBeInstanceOf(Date);
46+
expect(note.updatedAt).toBeInstanceOf(Date);
47+
expect(note.createdAt.toISOString()).toBe(obj.createdAt);
48+
expect(note.updatedAt.toISOString()).toBe(obj.updatedAt);
49+
});
50+
51+
test('should convert a note to an object', () => {
52+
const note = new Note('789', 'To Object', 'Converting to object', new Date('2023-03-01'), new Date('2023-03-02'));
53+
54+
const obj = note.toObject();
55+
56+
expect(obj).toEqual({
57+
id: note.id,
58+
title: note.title,
59+
content: note.content,
60+
createdAt: note.createdAt,
61+
updatedAt: note.updatedAt
62+
});
63+
});
64+
});
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import express from 'express';
2+
import request from 'supertest';
3+
import { createNotesRouter } from '../../src/routes/notes-routes.js';
4+
import { Note } from '../../src/models/note.js';
5+
6+
// Mock repository for testing
7+
class MockNoteRepository {
8+
constructor() {
9+
this.notes = [];
10+
this.nextId = 1;
11+
}
12+
13+
async findAll() {
14+
return [...this.notes];
15+
}
16+
17+
async findById(id) {
18+
const note = this.notes.find(n => n.id === id);
19+
return note || null;
20+
}
21+
22+
async create(noteData) {
23+
const id = String(this.nextId++);
24+
const note = new Note(
25+
id,
26+
noteData.title,
27+
noteData.content,
28+
new Date(),
29+
new Date()
30+
);
31+
this.notes.push(note);
32+
return note;
33+
}
34+
35+
async update(id, noteData) {
36+
const index = this.notes.findIndex(n => n.id === id);
37+
if (index === -1) return null;
38+
39+
const note = this.notes[index];
40+
note.title = noteData.title;
41+
note.content = noteData.content;
42+
note.updatedAt = new Date();
43+
44+
return note;
45+
}
46+
47+
async delete(id) {
48+
const index = this.notes.findIndex(n => n.id === id);
49+
if (index === -1) return false;
50+
51+
this.notes.splice(index, 1);
52+
return true;
53+
}
54+
}
55+
56+
describe('Notes Routes', () => {
57+
let app;
58+
let repository;
59+
60+
beforeEach(() => {
61+
repository = new MockNoteRepository();
62+
app = express();
63+
app.use(express.json());
64+
app.use('/api/notes', createNotesRouter(repository));
65+
});
66+
67+
describe('GET /api/notes', () => {
68+
test('should return empty array when no notes exist', async () => {
69+
const response = await request(app).get('/api/notes');
70+
71+
expect(response.status).toBe(200);
72+
expect(response.body).toEqual([]);
73+
});
74+
75+
test('should return all notes', async () => {
76+
// Add some test notes
77+
await repository.create({ title: 'Note 1', content: 'Content 1' });
78+
await repository.create({ title: 'Note 2', content: 'Content 2' });
79+
80+
const response = await request(app).get('/api/notes');
81+
82+
expect(response.status).toBe(200);
83+
expect(response.body.length).toBe(2);
84+
expect(response.body[0].title).toBe('Note 1');
85+
expect(response.body[1].title).toBe('Note 2');
86+
});
87+
});
88+
89+
describe('POST /api/notes', () => {
90+
test('should create a new note', async () => {
91+
const noteData = { title: 'New Note', content: 'New Content' };
92+
93+
const response = await request(app)
94+
.post('/api/notes')
95+
.send(noteData)
96+
.set('Content-Type', 'application/json');
97+
98+
expect(response.status).toBe(201);
99+
expect(response.body.title).toBe(noteData.title);
100+
expect(response.body.content).toBe(noteData.content);
101+
expect(response.body.id).toBeDefined();
102+
});
103+
104+
test('should return 400 if title or content is missing', async () => {
105+
const response = await request(app)
106+
.post('/api/notes')
107+
.send({ title: 'Missing Content' })
108+
.set('Content-Type', 'application/json');
109+
110+
expect(response.status).toBe(400);
111+
expect(response.body.error).toBeDefined();
112+
});
113+
});
114+
});

jest.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
transform: {},
3+
moduleNameMapper: {
4+
'^(\\.{1,2}/.*)\\.js$': '$1',
5+
},
6+
testEnvironment: 'node',
7+
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
8+
verbose: true
9+
};

0 commit comments

Comments
 (0)