Add tests of task services
This commit is contained in:
parent
b7f4c3687e
commit
7795e5d674
11 changed files with 4074 additions and 79 deletions
19
angular.json
19
angular.json
|
|
@ -77,24 +77,9 @@
|
|||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"builder": "@angular-builders/jest:run",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
"configPath": "./jest.config.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
jest.config.js
Normal file
6
jest.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const presets = require("jest-preset-angular/presets");
|
||||
|
||||
module.exports = {
|
||||
...presets.createCjsPreset(),
|
||||
setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"],
|
||||
};
|
||||
3718
package-lock.json
generated
3718
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
|
@ -8,6 +8,7 @@
|
|||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"test:watch": "ng test --watch",
|
||||
"serve:ssr:my-task-board-angular": "node dist/my-task-board-angular/server/server.mjs"
|
||||
},
|
||||
"private": true,
|
||||
|
|
@ -29,20 +30,18 @@
|
|||
"postcss": "^8.5.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "^19.0.1",
|
||||
"@angular-devkit/build-angular": "^19.2.7",
|
||||
"@angular/cli": "^19.2.7",
|
||||
"@angular/compiler-cli": "^19.2.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^18.18.0",
|
||||
"typescript": "~5.7.2"
|
||||
"jest": "^29.7.0",
|
||||
"typescript": "~5.7.2",
|
||||
"zone.js": "^0.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// npm uninstall @types/jasmine jasmine-core karma, karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
|
||||
|
||||
// npm install --save-dev jest @types/jest @angular-builders/jest @angular-builders/jest-preset-angular jest-preset-angular ts-jest
|
||||
|
|
|
|||
3
setup-jest.ts
Normal file
3
setup-jest.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
// setupZoneTestEnv({});
|
||||
48
src/app/__mocks__/task.ts
Normal file
48
src/app/__mocks__/task.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { Task } from '../features/task/model/task.model';
|
||||
|
||||
export const tasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Ir na academia',
|
||||
isCompleted: false,
|
||||
categoryId: '5',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Comprar pão na padaria',
|
||||
isCompleted: true,
|
||||
categoryId: '1',
|
||||
},
|
||||
];
|
||||
|
||||
export const task: Task = {
|
||||
id: '1',
|
||||
title: 'Ir na academia',
|
||||
isCompleted: false,
|
||||
categoryId: '5',
|
||||
};
|
||||
|
||||
export const TASK_INTERNAL_SERVER_ERROR_RESPONSE: {
|
||||
status: number;
|
||||
statusText: string;
|
||||
} = {
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
}
|
||||
|
||||
export const TASK_UNPROCESSIBLE_ENTITY_RESPONSE: {
|
||||
status: number;
|
||||
statusText: string;
|
||||
} = {
|
||||
status: 422,
|
||||
statusText: 'Unprocessable Entity',
|
||||
};
|
||||
|
||||
|
||||
export const TASK_NOT_FOUND_RESPONSE: {
|
||||
status: number;
|
||||
statusText: string;
|
||||
} = {
|
||||
status: 404,
|
||||
statusText: 'Not found',
|
||||
};
|
||||
18
src/app/app.component.spec.ts
Normal file
18
src/app/app.component.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
providers: [provideHttpClient()],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it ('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
||||
270
src/app/features/task/service/task.service.spec.ts
Normal file
270
src/app/features/task/service/task.service.spec.ts
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
import { task, TASK_INTERNAL_SERVER_ERROR_RESPONSE, TASK_UNPROCESSIBLE_ENTITY_RESPONSE, tasks } from './../../../__mocks__/task';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { HttpErrorResponse, provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { TaskService } from './task.service';
|
||||
|
||||
describe('TaskService', () => {
|
||||
let taskService: TaskService;
|
||||
let httpTestingController: HttpTestingController;
|
||||
|
||||
const MOCKED_TASKS = tasks;
|
||||
const MOCKED_TASK = task;
|
||||
const baseURL = 'http://localhost:3000';
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideHttpClient(), provideHttpClientTesting()],
|
||||
});
|
||||
|
||||
taskService = TestBed.inject(TaskService);
|
||||
httpTestingController = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
it('should be created ', () => {
|
||||
expect(taskService).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get tasks', () => {
|
||||
const sortedTasks = taskService.getSortedTasks(MOCKED_TASKS);
|
||||
expect(sortedTasks[0].title).toEqual('Comprar pão na padaria');
|
||||
});
|
||||
|
||||
describe('getTasks', () => {
|
||||
it('should return a list of tasks', waitForAsync(() => {
|
||||
taskService.getTasks().subscribe((response) => {
|
||||
expect(response).toEqual(MOCKED_TASKS);
|
||||
expect(taskService.tasks()).toEqual(MOCKED_TASKS);
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(`${baseURL}/tasks`);
|
||||
|
||||
req.flush(MOCKED_TASKS);
|
||||
|
||||
expect(taskService.tasks()).toEqual(MOCKED_TASKS);
|
||||
expect(req.request.method).toEqual('GET');
|
||||
}));
|
||||
|
||||
it('should throw and error when server return Internal Server Error', waitForAsync(() => {
|
||||
let httpErrorResponse: HttpErrorResponse | undefined;
|
||||
|
||||
taskService.getTasks().subscribe({
|
||||
next: () => {
|
||||
fail('failed to fetch the tasks list');
|
||||
},
|
||||
error: (error: HttpErrorResponse) => {
|
||||
httpErrorResponse = error;
|
||||
},
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(`${baseURL}/tasks`);
|
||||
|
||||
req.flush('Internal Server Error', TASK_INTERNAL_SERVER_ERROR_RESPONSE);
|
||||
|
||||
if (!httpErrorResponse) {
|
||||
throw new Error('Error neets to be defined');
|
||||
}
|
||||
|
||||
expect(httpErrorResponse.status).toEqual(500);
|
||||
expect(httpErrorResponse.statusText).toEqual('Internal Server Error');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('creatTask', () => {
|
||||
it('should create a new task', waitForAsync(() => {
|
||||
taskService.createTask(MOCKED_TASK).subscribe(() => {
|
||||
expect(taskService.tasks()[0]).toEqual(MOCKED_TASK);
|
||||
expect(taskService.tasks().length).toEqual(1);
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(`${baseURL}/tasks`);
|
||||
|
||||
req.flush(MOCKED_TASK);
|
||||
|
||||
expect(req.request.method).toEqual('POST');
|
||||
}));
|
||||
|
||||
it('should throw unprocessible entity with invalid body when create a task', waitForAsync(() => {
|
||||
let httpErrorResponse: HttpErrorResponse | undefined;
|
||||
|
||||
taskService.createTask(MOCKED_TASK).subscribe({
|
||||
next: () => {
|
||||
fail('failed to fetch a new task');
|
||||
},
|
||||
error: (error: HttpErrorResponse) => {
|
||||
httpErrorResponse = error;
|
||||
},
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(`${baseURL}/tasks`);
|
||||
|
||||
req.flush('Unprocessable Entity', TASK_UNPROCESSIBLE_ENTITY_RESPONSE);
|
||||
|
||||
if (!httpErrorResponse) {
|
||||
throw new Error('Error neets to be defined');
|
||||
}
|
||||
|
||||
expect(httpErrorResponse.status).toEqual(422);
|
||||
|
||||
expect(httpErrorResponse.statusText).toEqual('Unprocessable Entity');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('updateTask', () => {
|
||||
it('should update a task', waitForAsync(() => {
|
||||
taskService.tasks.set([MOCKED_TASK]);
|
||||
|
||||
const updatedTask = MOCKED_TASK;
|
||||
updatedTask.title = 'Ir na academia treinar perna';
|
||||
|
||||
taskService.updateTask(updatedTask).subscribe(() => {
|
||||
expect(taskService.tasks()[0].title).toEqual(
|
||||
'Ir na academia treinar perna'
|
||||
);
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${baseURL}/tasks/${updatedTask.id}`
|
||||
);
|
||||
|
||||
req.flush(MOCKED_TASK);
|
||||
|
||||
expect(req.request.method).toEqual('PUT');
|
||||
}));
|
||||
|
||||
it('should throw unprocessible entity with invalid body when update a task', waitForAsync(() => {
|
||||
let httpErrorResponse: HttpErrorResponse | undefined;
|
||||
|
||||
taskService.tasks.set([MOCKED_TASK]);
|
||||
|
||||
const updatedTask = MOCKED_TASK;
|
||||
updatedTask.title = 'Ir na academia treinar perna';
|
||||
|
||||
taskService.updateTask(MOCKED_TASK).subscribe({
|
||||
next: () => {
|
||||
fail('failed to update a task');
|
||||
},
|
||||
error: (error: HttpErrorResponse) => {
|
||||
httpErrorResponse = error;
|
||||
},
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${baseURL}/tasks/${updatedTask.id}`
|
||||
);
|
||||
|
||||
req.flush('Unprocessable Entity', TASK_UNPROCESSIBLE_ENTITY_RESPONSE);
|
||||
|
||||
if (!httpErrorResponse) {
|
||||
throw new Error('Error neets to be defined');
|
||||
}
|
||||
|
||||
expect(httpErrorResponse.status).toEqual(422);
|
||||
|
||||
expect(httpErrorResponse.statusText).toEqual('Unprocessable Entity');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('updateIsCompletedStatus', () => {
|
||||
it('should update IsCompletedStatus of a task', waitForAsync(() => {
|
||||
const updatedTask = MOCKED_TASK;
|
||||
const methodUrl = `${baseURL}/tasks/${updatedTask.id}`;
|
||||
|
||||
taskService.tasks.set(MOCKED_TASKS);
|
||||
|
||||
taskService
|
||||
.updateIsCompletedStatus(MOCKED_TASK.id, true)
|
||||
.subscribe(() => {
|
||||
expect(taskService.tasks()[0].isCompleted).toBeTruthy();
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(methodUrl);
|
||||
|
||||
req.flush({ isCompleted: true });
|
||||
|
||||
expect(req.request.method).toEqual('PATCH');
|
||||
}));
|
||||
|
||||
it('should throw and error when update a tasks isCompleted status', waitForAsync(() => {
|
||||
let httpErrorResponse: HttpErrorResponse | undefined;
|
||||
|
||||
const updatedTask = MOCKED_TASK;
|
||||
const methodUrl = `${baseURL}/tasks/${updatedTask.id}`;
|
||||
|
||||
taskService.tasks.set(MOCKED_TASKS);
|
||||
|
||||
taskService.updateIsCompletedStatus(updatedTask.id, true).subscribe({
|
||||
next: () => {
|
||||
fail('failed to update a task isCompleted status');
|
||||
},
|
||||
error: (error: HttpErrorResponse) => {
|
||||
httpErrorResponse = error;
|
||||
},
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(methodUrl);
|
||||
|
||||
req.flush('Unprocessable Entity', TASK_UNPROCESSIBLE_ENTITY_RESPONSE);
|
||||
|
||||
if (!httpErrorResponse) {
|
||||
throw new Error('Error neets to be defined');
|
||||
}
|
||||
|
||||
expect(httpErrorResponse.status).toEqual(422);
|
||||
|
||||
expect(httpErrorResponse.statusText).toEqual('Unprocessable Entity');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('deleteTask', () => {
|
||||
it('should delete a task', waitForAsync(() => {
|
||||
taskService.tasks.set([MOCKED_TASK]);
|
||||
|
||||
taskService.deleteTask(MOCKED_TASK.id).subscribe(() => {
|
||||
expect(taskService.tasks().length).toEqual(0);
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${baseURL}/tasks/${MOCKED_TASK.id}`
|
||||
);
|
||||
|
||||
req.flush(null);
|
||||
|
||||
expect(req.request.method).toEqual('DELETE');
|
||||
}));
|
||||
|
||||
it('should throw unprocessible entity with invalid body when delete a task', waitForAsync(() => {
|
||||
let httpErrorResponse: HttpErrorResponse | undefined;
|
||||
|
||||
taskService.tasks.set([MOCKED_TASK]);
|
||||
|
||||
taskService.deleteTask(MOCKED_TASK.id).subscribe({
|
||||
next: () => {
|
||||
fail('failed to delete a task');
|
||||
},
|
||||
error: (error: HttpErrorResponse) => {
|
||||
httpErrorResponse = error;
|
||||
},
|
||||
});
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${baseURL}/tasks/${MOCKED_TASK.id}`
|
||||
);
|
||||
|
||||
req.flush('Unprocessable Entity', TASK_UNPROCESSIBLE_ENTITY_RESPONSE);
|
||||
|
||||
if (!httpErrorResponse) {
|
||||
throw new Error('Error neets to be defined');
|
||||
}
|
||||
|
||||
expect(httpErrorResponse.status).toEqual(422);
|
||||
|
||||
expect(httpErrorResponse.statusText).toEqual('Unprocessable Entity');
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -19,19 +19,27 @@ export class TaskService {
|
|||
public getTasks(): Observable<Task[]> {
|
||||
return this.httpClient.get<Task[]>(`${this._apiUrl}/tasks`).pipe(
|
||||
tap((tasks) => {
|
||||
// Sort the tasks by title
|
||||
const sortedTasks = this.getSortedTasks(tasks);
|
||||
this.tasks.set(sortedTasks);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public createTask(task: Partial<Task>): Observable<Task> {
|
||||
return this.httpClient.post<Task>(`${this._apiUrl}/tasks`, task);
|
||||
public getSortedTasks(tasks: Task[]): Task[] {
|
||||
return tasks.sort((a, b) => a.title?.localeCompare(b.title));
|
||||
}
|
||||
|
||||
public getSortedTasks(tasks: Task[]): Task[] {
|
||||
return tasks.sort((a, b) => a.title.localeCompare(b.title));
|
||||
public createTask(task: Partial<Task>): Observable<Task> {
|
||||
return this.httpClient
|
||||
.post<Task>(`${this._apiUrl}/tasks`, task)
|
||||
.pipe(tap((tasks) => this.insertATaskInTheTasksList(tasks)));
|
||||
}
|
||||
|
||||
public insertATaskInTheTasksList(newTask: Task): void {
|
||||
const updatedTasks = [...this.tasks(), newTask];
|
||||
const sortedTasks = this.getSortedTasks(updatedTasks);
|
||||
|
||||
this.tasks.set(sortedTasks);
|
||||
}
|
||||
|
||||
public insertATasksList(newTask: Task): void {
|
||||
|
|
@ -41,32 +49,32 @@ export class TaskService {
|
|||
}
|
||||
|
||||
public updateTask(updatedTask: Task): Observable<Task> {
|
||||
return this.httpClient.put<Task>(
|
||||
`${this._apiUrl}/tasks/${updatedTask.id}`,
|
||||
updatedTask
|
||||
);
|
||||
return this.httpClient
|
||||
.put<Task>(`${this._apiUrl}/tasks/${updatedTask.id}`, updatedTask)
|
||||
.pipe(tap((tasks) => this.updateTaskInTheTasksList(tasks)));
|
||||
}
|
||||
|
||||
public updateIsCompletedStatus(taskId: string, isCompleted: boolean): Observable<Task> {
|
||||
return this.httpClient.patch<Task>(
|
||||
`${this._apiUrl}/tasks/${taskId}`,
|
||||
{ isCompleted }
|
||||
);
|
||||
public updateIsCompletedStatus(taskId: string, isCompleted: boolean ): Observable<Task> {
|
||||
return this.httpClient
|
||||
.patch<Task>(`${this._apiUrl}/tasks/${taskId}`, { isCompleted })
|
||||
.pipe(tap((tasks) => this.updateTaskInTheTasksList(tasks)));
|
||||
}
|
||||
|
||||
public updateTaskInTheTasksList(updatedTask: Task): void {
|
||||
this.tasks.update((tasks) => {
|
||||
const allTasksWithUpdatedTaskRemoved = tasks.filter(
|
||||
(task) => task.id !== updatedTask.id
|
||||
);
|
||||
this.tasks.update((tasks) => {
|
||||
const allTasksWithUpdatedTaskRemoved = tasks.filter(
|
||||
(task) => task.id !== updatedTask.id
|
||||
);
|
||||
const updatedTaskList = [...allTasksWithUpdatedTaskRemoved, updatedTask];
|
||||
|
||||
return this.getSortedTasks(updatedTaskList);
|
||||
});}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public deleteTask(taskId: string): Observable<Task> {
|
||||
return this.httpClient.delete<Task>(`${this._apiUrl}/tasks/${taskId}`);
|
||||
return this.httpClient
|
||||
.delete<Task>(`${this._apiUrl}/tasks/${taskId}`)
|
||||
.pipe(tap(() => this.deleteATaksInTheTasksList(taskId)));
|
||||
}
|
||||
|
||||
public deleteATaksInTheTasksList(taskId: string): void {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
],
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
"jest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue