├── .eslintrc.js
├── .gitignore
├── README.md
├── libs
└── common
│ ├── src
│ ├── database
│ │ ├── abstract.repository.ts
│ │ ├── abstract.schema.ts
│ │ ├── database.module.ts
│ │ └── index.ts
│ └── index.ts
│ └── tsconfig.lib.json
├── nest-cli.json
├── package.json
├── pnpm-lock.yaml
├── src
├── app.module.ts
├── main.ts
└── users
│ ├── __mocks__
│ └── user.service.mock.ts
│ ├── db
│ ├── user.repository.ts
│ └── user.schema.ts
│ ├── dto
│ ├── create.user.dto.ts
│ ├── index.ts
│ └── update.user.dto.ts
│ ├── test
│ ├── stubs
│ │ └── user.stub.ts
│ ├── support
│ │ └── user.model.ts
│ ├── user.controller.spec.ts
│ └── user.repository.spec.ts
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
├── test
├── app.e2e-spec.ts
└── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 | /.env
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | pnpm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 |
15 | # OS
16 | .DS_Store
17 |
18 | # Tests
19 | /coverage
20 | /.nyc_output
21 |
22 | # IDEs and editors
23 | /.idea
24 | .project
25 | .classpath
26 | .c9/
27 | *.launch
28 | .settings/
29 | *.sublime-workspace
30 |
31 | # IDE - VSCode
32 | .vscode/*
33 | !.vscode/settings.json
34 | !.vscode/tasks.json
35 | !.vscode/launch.json
36 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6 | [circleci-url]: https://circleci.com/gh/nestjs/nest
7 |
8 | A progressive Node.js framework for building efficient and scalable server-side applications.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | ## Description
26 |
27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
28 |
29 | ## Installation
30 |
31 | ```bash
32 | $ pnpm install
33 | ```
34 |
35 | ## Running the app
36 |
37 | ```bash
38 | # development
39 | $ pnpm run start
40 |
41 | # watch mode
42 | $ pnpm run start:dev
43 |
44 | # production mode
45 | $ pnpm run start:prod
46 | ```
47 |
48 | ## Test
49 |
50 | ```bash
51 | # unit tests
52 | $ pnpm run test
53 |
54 | # e2e tests
55 | $ pnpm run test:e2e
56 |
57 | # test coverage
58 | $ pnpm run test:cov
59 | ```
60 |
61 | ## Support
62 |
63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
64 |
65 | ## Stay in touch
66 |
67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
68 | - Website - [https://nestjs.com](https://nestjs.com/)
69 | - Twitter - [@nestframework](https://twitter.com/nestframework)
70 |
71 | ## License
72 |
73 | Nest is [MIT licensed](LICENSE).
74 |
--------------------------------------------------------------------------------
/libs/common/src/database/abstract.repository.ts:
--------------------------------------------------------------------------------
1 | import { DeleteOptions, UpdateFilter } from "mongodb";
2 | import mongoose, { Aggregate, AggregateOptions, Document, FilterQuery, Model, PipelineStage, PopulateOptions, ProjectionType, QueryOptions, Types, UpdateQuery } from "mongoose";
3 | import { Logger, NotFoundException } from "@nestjs/common";
4 | import { AbstractDocument } from "./abstract.schema";
5 |
6 | export abstract class AbstractRepository {
7 | constructor(
8 | protected readonly entityModel: Model,
9 | ) { };
10 |
11 | async create(
12 | document: Omit,
13 | ): Promise {
14 | const entity = new this.entityModel({
15 | _id: new Types.ObjectId(),
16 | ...document
17 | });
18 | return ((await entity.save()).toJSON()) as unknown as TDocument
19 | }
20 |
21 | async findOne(
22 | filterQuery: FilterQuery,
23 | projection?: ProjectionType,
24 | options?: QueryOptions
25 | ) {
26 | const document = this.entityModel.findOne(
27 | filterQuery,
28 | projection,
29 | options
30 | ).lean(true).exec();
31 | if (!document) {
32 | throw new NotFoundException("no documents found")
33 | }
34 | return document
35 | }
36 |
37 | async findByIdAndUpdate(
38 | id: mongoose.ObjectId | string,
39 | update?: UpdateQuery,
40 | options?: QueryOptions
41 | ) {
42 | const document = this.entityModel.findByIdAndUpdate(
43 | id,
44 | update,
45 | options
46 | ).lean(true).exec();
47 | if (!document) {
48 | throw new NotFoundException("no documents found")
49 | }
50 | return document;
51 | }
52 |
53 | async find(
54 | entityFilterQuery: FilterQuery,
55 | projection?: ProjectionType,
56 | options?: QueryOptions
57 | ) {
58 | const documents = this.entityModel.find(
59 | entityFilterQuery,
60 | projection,
61 | options
62 | ).lean(true).exec();
63 | if (!documents) {
64 | throw new NotFoundException("no documents found")
65 | }
66 | return documents
67 | }
68 |
69 | async findById(
70 | id: mongoose.ObjectId | string,
71 | projection?: Record,
72 | options?: QueryOptions
73 | ) {
74 | const document = await this.entityModel.findById(
75 | id,
76 | { _id: 0, __v: 0, ...projection },
77 | options
78 | ).exec();
79 | if (!document) {
80 | throw new NotFoundException("no documents found")
81 | }
82 | return document;
83 | }
84 |
85 | async findOneAndUpdate(
86 | filterQuery: FilterQuery,
87 | updateQueryData: FilterQuery
88 | ) {
89 | const document = this.entityModel.findOneAndUpdate(
90 | filterQuery,
91 | updateQueryData
92 | ).exec();
93 | if (!document) {
94 | throw new NotFoundException("no documents found")
95 | }
96 | return document;
97 | }
98 |
99 | async updateMany(
100 | filterQuery: FilterQuery,
101 | updateQueryData: FilterQuery
102 | ) {
103 | const document = this.entityModel.updateMany(
104 | filterQuery,
105 | updateQueryData
106 | ).exec();
107 | if (!document) {
108 | throw new NotFoundException("no documents found")
109 | }
110 | return document;
111 | }
112 |
113 | async deleteMany(
114 | filterQuery: FilterQuery,
115 | options?: DeleteOptions
116 | ): Promise {
117 | const deleteResult = await this.entityModel.deleteMany(filterQuery);
118 | return deleteResult.deletedCount >= 1
119 | }
120 |
121 | async deleteOne(
122 | filterQuery: FilterQuery
123 | ): Promise {
124 | const deleteResult = await this.entityModel.deleteOne(filterQuery);
125 | return deleteResult.deletedCount >= 1;
126 | }
127 |
128 | async populate(
129 | docs: Array,
130 | options: PopulateOptions | Array
131 | ): Promise> {
132 | return this.entityModel.populate(docs, options);
133 | }
134 |
135 | aggregate(
136 | pipeline: PipelineStage[],
137 | options?: AggregateOptions,
138 | ): Aggregate> {
139 | return this.entityModel.aggregate(pipeline, options)
140 | }
141 |
142 | }
--------------------------------------------------------------------------------
/libs/common/src/database/abstract.schema.ts:
--------------------------------------------------------------------------------
1 | import { Prop, Schema } from "@nestjs/mongoose";
2 | import { Types } from "mongoose";
3 |
4 | @Schema()
5 | export abstract class AbstractDocument {
6 | @Prop({ type: Types.ObjectId })
7 | _id: string
8 | }
--------------------------------------------------------------------------------
/libs/common/src/database/database.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "@nestjs/common";
2 | import { ModelDefinition, MongooseModule } from "@nestjs/mongoose";
3 | import { ConfigService } from '@nestjs/config';
4 |
5 | @Module({
6 | imports: [
7 | MongooseModule.forRootAsync({
8 | useFactory: (configService: ConfigService) => ({
9 | uri: configService.get("MONGODB_URI")
10 | }),
11 | inject: [ConfigService]
12 | })
13 | ]
14 | })
15 | export class DatabaseModule {
16 | static forFeature(models: ModelDefinition[]) {
17 | return MongooseModule.forFeature(models);
18 | }
19 | }
--------------------------------------------------------------------------------
/libs/common/src/database/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./abstract.schema"
2 | export * from "./abstract.repository"
3 | export * from "./database.module"
--------------------------------------------------------------------------------
/libs/common/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./database"
--------------------------------------------------------------------------------
/libs/common/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "outDir": "../../dist/libs/common"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src",
5 | "compilerOptions": {
6 | "deleteOutDir": true,
7 | "webpack": true
8 | },
9 | "projects": {
10 | "common": {
11 | "type": "library",
12 | "root": "libs/common",
13 | "entryFile": "index",
14 | "sourceRoot": "libs/common/src",
15 | "compilerOptions": {
16 | "tsConfigPath": "libs/common/tsconfig.lib.json"
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unit-test",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "build": "nest build",
10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
11 | "start": "nest start",
12 | "start:dev": "nest start --watch",
13 | "start:debug": "nest start --debug --watch",
14 | "start:prod": "node dist/main",
15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "test:cov": "jest --coverage",
19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20 | "test:e2e": "jest --config ./test/jest-e2e.json"
21 | },
22 | "dependencies": {
23 | "@nestjs/common": "^10.0.0",
24 | "@nestjs/config": "^3.2.3",
25 | "@nestjs/core": "^10.0.0",
26 | "@nestjs/mongoose": "^10.0.10",
27 | "@nestjs/platform-express": "^10.0.0",
28 | "dotenv": "^16.4.5",
29 | "mongoose": "^8.6.3",
30 | "reflect-metadata": "^0.1.13",
31 | "rxjs": "^7.8.1"
32 | },
33 | "devDependencies": {
34 | "@nestjs/cli": "^10.0.0",
35 | "@nestjs/schematics": "^10.0.0",
36 | "@nestjs/testing": "^10.0.0",
37 | "@types/express": "^4.17.17",
38 | "@types/jest": "^29.5.2",
39 | "@types/node": "^20.3.1",
40 | "@types/supertest": "^2.0.12",
41 | "@typescript-eslint/eslint-plugin": "^6.0.0",
42 | "@typescript-eslint/parser": "^6.0.0",
43 | "eslint": "^8.42.0",
44 | "eslint-config-prettier": "^9.0.0",
45 | "eslint-plugin-prettier": "^5.0.0",
46 | "jest": "^29.5.0",
47 | "prettier": "^3.0.0",
48 | "source-map-support": "^0.5.21",
49 | "supertest": "^6.3.3",
50 | "ts-jest": "^29.1.0",
51 | "ts-loader": "^9.4.3",
52 | "ts-node": "^10.9.1",
53 | "tsconfig-paths": "^4.2.0",
54 | "typescript": "^5.1.3"
55 | },
56 | "jest": {
57 | "moduleFileExtensions": [
58 | "js",
59 | "json",
60 | "ts"
61 | ],
62 | "rootDir": ".",
63 | "testRegex": ".*\\.spec\\.ts$",
64 | "transform": {
65 | "^.+\\.(t|j)s$": "ts-jest"
66 | },
67 | "collectCoverageFrom": [
68 | "**/*.(t|j)s"
69 | ],
70 | "coverageDirectory": "./coverage",
71 | "testEnvironment": "node",
72 | "roots": [
73 | "/src/",
74 | "/libs/"
75 | ],
76 | "moduleNameMapper": {
77 | "^@app/common(|/.*)$": "/libs/common/src/$1"
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { DatabaseModule } from '@app/common';
2 | import { Module } from '@nestjs/common';
3 | import { UsersModule } from './users/users.module';
4 |
5 | @Module({
6 | imports: [
7 | DatabaseModule,
8 | UsersModule
9 | ],
10 | controllers: [],
11 | providers: [],
12 | })
13 | export class AppModule { }
14 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 |
4 | async function bootstrap() {
5 | const app = await NestFactory.create(AppModule);
6 | await app.listen(process.env.PORT || Number(3000));
7 | }
8 | bootstrap();
9 |
--------------------------------------------------------------------------------
/src/users/__mocks__/user.service.mock.ts:
--------------------------------------------------------------------------------
1 | import { userStub } from "../test/stubs/user.stub";
2 |
3 | export const UsersService = jest.fn().mockReturnValue({
4 | getUserById: jest.fn().mockResolvedValue(userStub()),
5 | getUsers: jest.fn().mockResolvedValue([userStub()]),
6 | createUser: jest.fn().mockResolvedValue(userStub()),
7 | updateUser: jest.fn().mockResolvedValue(userStub()),
8 | })
--------------------------------------------------------------------------------
/src/users/db/user.repository.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@nestjs/common";
2 | import { InjectModel } from "@nestjs/mongoose";
3 | import { FilterQuery, Model } from "mongoose";
4 | import { AbstractRepository } from "@app/common";
5 | import { User } from "./user.schema";
6 |
7 | @Injectable()
8 | export class UsersRepository extends AbstractRepository {
9 | constructor(
10 | @InjectModel(User.name)
11 | private userSchema: Model
12 | ) {
13 | super(userSchema);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/users/db/user.schema.ts:
--------------------------------------------------------------------------------
1 | import { AbstractDocument } from "@app/common";
2 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
3 | import { Document } from 'mongoose';
4 |
5 | export type UserDocument = User & Document;
6 |
7 | @Schema()
8 | export class User extends AbstractDocument {
9 | @Prop()
10 | userId: string;
11 |
12 | @Prop()
13 | email: string;
14 |
15 | @Prop()
16 | age: number;
17 |
18 | @Prop([String])
19 | favoriteFoods: string[]
20 | }
21 |
22 | export const UserSchema = SchemaFactory.createForClass(User);
--------------------------------------------------------------------------------
/src/users/dto/create.user.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateUserDto {
2 | email: string;
3 | age: number;
4 | }
--------------------------------------------------------------------------------
/src/users/dto/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./create.user.dto"
2 | export * from "./update.user.dto"
--------------------------------------------------------------------------------
/src/users/dto/update.user.dto.ts:
--------------------------------------------------------------------------------
1 | export class UpdateUserDto {
2 | favoriteFoods: string[]
3 | age: number;
4 | }
--------------------------------------------------------------------------------
/src/users/test/stubs/user.stub.ts:
--------------------------------------------------------------------------------
1 | import { User } from "src/users/db/user.schema"
2 |
3 | export const userStub = (): User => {
4 | return {
5 | _id: "object id",
6 | userId: 'sadra3523423',
7 | email: 'sadra@test.com',
8 | age: 22,
9 | favoriteFoods: ['cake', 'pizza']
10 | }
11 | }
--------------------------------------------------------------------------------
/src/users/test/support/user.model.ts:
--------------------------------------------------------------------------------
1 | import { MockModel } from "../../../database/test/support/mock.model";
2 | import { User } from "../../schemas/user.schema";
3 | import { userStub } from "../stubs/user.stub";
4 |
5 | export class UserModel extends MockModel {
6 | protected entityStub = userStub()
7 | }
8 |
--------------------------------------------------------------------------------
/src/users/test/user.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test } from "@nestjs/testing"
2 | import { CreateUserDto } from "../dto/create-user.dto";
3 | import { UpdateUserDto } from "../dto/update-user.dto";
4 | import { User } from "../schemas/user.schema";
5 | import { UsersController } from "../users.controller"
6 | import { UsersService } from "../users.service"
7 | import { userStub } from "./stubs/user.stub";
8 |
9 | jest.mock('../users.service');
10 |
11 | describe('UsersController', () => {
12 | let usersController: UsersController;
13 | let usersService: UsersService;
14 |
15 | beforeEach(async () => {
16 | const moduleRef = await Test.createTestingModule({
17 | imports: [],
18 | controllers: [UsersController],
19 | providers: [UsersService]
20 | }).compile();
21 |
22 | usersController = moduleRef.get(UsersController);
23 | usersService = moduleRef.get(UsersService);
24 | jest.clearAllMocks();
25 | })
26 |
27 | describe('getUser', () => {
28 | describe('when getUser is called', () => {
29 | let user: User;
30 |
31 | beforeEach(async () => {
32 | user = await usersController.getUser(userStub().userId)
33 | })
34 |
35 | test('then it should call usersService', () => {
36 | expect(usersService.getUserById).toBeCalledWith(userStub().userId);
37 | })
38 |
39 | test('then is should return a user', () => {
40 | expect(user).toEqual(userStub());
41 | })
42 | })
43 | })
44 |
45 | describe('getUsers', () => {
46 | describe('when getUsers is called', () => {
47 | let users: User[];
48 |
49 | beforeEach(async () => {
50 | users = await usersController.getUsers();
51 | })
52 |
53 | test('then it should call usersService', () => {
54 | expect(usersService.getUsers).toHaveBeenCalled();
55 | })
56 |
57 | test('then it should return users', () => {
58 | expect(users).toEqual([userStub()])
59 | })
60 | })
61 | })
62 |
63 | describe('createUser', () => {
64 | describe('when createUser is called', () => {
65 | let user: User;
66 | let createUserDto: CreateUserDto
67 |
68 | beforeEach(async () => {
69 | createUserDto = {
70 | email: userStub().email,
71 | age: userStub().age
72 | }
73 | user = await usersController.createUser(createUserDto);
74 | })
75 |
76 | test('then it should call usersService', () => {
77 | expect(usersService.createUser).toHaveBeenCalledWith(createUserDto.email, createUserDto.age);
78 | })
79 |
80 | test('then it should return a user', () => {
81 | expect(user).toEqual(userStub())
82 | })
83 | })
84 | })
85 |
86 | describe('updateUser', () => {
87 | describe('when updateUser is called', () => {
88 | let user: User;
89 | let updateUserDto: UpdateUserDto;
90 |
91 | beforeEach(async () => {
92 | updateUserDto = {
93 | age: 98,
94 | favoriteFoods: ['pizza']
95 | }
96 | user = await usersController.updateUser(userStub().userId, updateUserDto);
97 | })
98 |
99 | test('then it should call usersService', () => {
100 | expect(usersService.updateUser).toHaveBeenCalledWith(userStub().userId, updateUserDto);
101 | })
102 |
103 | test('then it should return a user', () => {
104 | expect(user).toEqual(userStub())
105 | })
106 | })
107 | })
108 | })
--------------------------------------------------------------------------------
/src/users/test/user.repository.spec.ts:
--------------------------------------------------------------------------------
1 | import { getModelToken } from "@nestjs/mongoose"
2 | import { Test } from "@nestjs/testing"
3 | import { FilterQuery } from "mongoose"
4 | import { User } from "../schemas/user.schema"
5 | import { UsersRepository } from "../users.repository"
6 | import { userStub } from "./stubs/user.stub"
7 | import { UserModel } from "./support/user.model"
8 |
9 | describe('UsersRepository', () => {
10 | let usersRepository: UsersRepository;
11 |
12 | describe('find operations', () => {
13 | let userModel: UserModel;
14 | let userFilterQuery: FilterQuery;
15 |
16 | beforeEach(async () => {
17 | const moduleRef = await Test.createTestingModule({
18 | providers: [
19 | UsersRepository,
20 | {
21 | provide: getModelToken(User.name),
22 | useClass: UserModel
23 | }
24 | ]
25 | }).compile();
26 |
27 | usersRepository = moduleRef.get(UsersRepository);
28 | userModel = moduleRef.get(getModelToken(User.name));
29 |
30 | userFilterQuery = {
31 | userId: userStub().userId
32 | }
33 |
34 | jest.clearAllMocks();
35 | })
36 |
37 |
38 | describe('findOne', () => {
39 | describe('when findOne is called', () => {
40 | let user: User;
41 |
42 | beforeEach(async () => {
43 | jest.spyOn(userModel, 'findOne');
44 | user = await usersRepository.findOne(
45 | userFilterQuery
46 | );
47 | })
48 |
49 | test('then it should call the userModel', () => {
50 | expect(
51 | userModel.findOne
52 | ).toHaveBeenCalledWith(
53 | userFilterQuery,
54 | {
55 | _id: 0, __v: 0
56 | }
57 | );
58 | })
59 |
60 | test('then it should return a user', () => {
61 | expect(user).toEqual(userStub());
62 | })
63 | })
64 | })
65 |
66 | describe('find', () => {
67 | describe('when find is called', () => {
68 | let users: User[];
69 |
70 | beforeEach(async () => {
71 | jest.spyOn(userModel, 'find');
72 | users = await usersRepository.find(
73 | userFilterQuery
74 | );
75 | })
76 |
77 | test('then it should call the userModel', () => {
78 | expect(
79 | userModel.find
80 | ).toHaveBeenCalledWith(
81 | userFilterQuery
82 | );
83 | })
84 |
85 | test('then it should return a user', () => {
86 | expect(users).toEqual([userStub()]);
87 | })
88 | })
89 | })
90 |
91 | describe('findOneAndUpdate', () => {
92 | describe('when findOneAndUpdate is called', () => {
93 | let user: User;
94 |
95 | beforeEach(async () => {
96 | jest.spyOn(
97 | userModel,
98 | 'findOneAndUpdate'
99 | );
100 | user = await usersRepository.findOneAndUpdate(
101 | userFilterQuery,
102 | userStub()
103 | );
104 | })
105 |
106 | test('then it should call the userModel', () => {
107 | expect(
108 | userModel.findOneAndUpdate
109 | ).toHaveBeenCalledWith(
110 | userFilterQuery,
111 | userStub(),
112 | {
113 | new: true
114 | }
115 | );
116 | })
117 |
118 | test('then it should return a user', () => {
119 | expect(user).toEqual(userStub());
120 | })
121 | })
122 | })
123 | })
124 | describe('create operations', () => {
125 | beforeEach(async () => {
126 | const moduleRef = await Test.createTestingModule({
127 | providers: [
128 | UsersRepository,
129 | {
130 | provide: getModelToken(User.name),
131 | useValue: UserModel,
132 | },
133 | ],
134 | }).compile();
135 |
136 | usersRepository = moduleRef.get(UsersRepository);
137 | });
138 |
139 | describe('create', () => {
140 | describe('when create is called', () => {
141 | let user: User;
142 | let saveSpy: jest.SpyInstance;
143 | let constructorSpy: jest.SpyInstance;
144 |
145 | beforeEach(async () => {
146 | saveSpy = jest.spyOn(
147 | UserModel.prototype,
148 | 'save'
149 | );
150 | constructorSpy = jest.spyOn(
151 | UserModel.prototype,
152 | 'constructorSpy'
153 | );
154 | user = await usersRepository.create(
155 | userStub()
156 | );
157 | })
158 |
159 | test('then it should call the userModel', () => {
160 | expect(saveSpy).toHaveBeenCalled();
161 | expect(constructorSpy).toHaveBeenCalledWith(userStub())
162 | })
163 |
164 | test('then it should return a user', () => {
165 | expect(user).toEqual(userStub());
166 | })
167 | })
168 | })
169 | })
170 | })
171 |
172 |
--------------------------------------------------------------------------------
/src/users/users.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 | import { User } from './db/user.schema';
4 | import { CreateUserDto, UpdateUserDto } from './dto';
5 |
6 |
7 | @Controller('users')
8 | export class UsersController {
9 | constructor(private readonly usersService: UsersService) { }
10 |
11 | @Get(':userId')
12 | async getUser(
13 | @Param('userId') userId: string
14 | ): Promise {
15 | return this.usersService.getUserById(userId);
16 | }
17 |
18 | @Get()
19 | async getUsers(): Promise {
20 | return this.usersService.getUsers();
21 | }
22 |
23 | @Post()
24 | async createUser(
25 | @Body() createUserDto: CreateUserDto
26 | ): Promise {
27 | return this.usersService.createUser(createUserDto.email, createUserDto.age)
28 | }
29 |
30 | @Patch(':userId')
31 | async updateUser(
32 | @Param('userId') userId: string,
33 | @Body() updateUserDto: UpdateUserDto
34 | ): Promise {
35 | return this.usersService.updateUser(userId, updateUserDto);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 | import { UsersController } from './users.controller';
4 | import { MongooseModule } from '@nestjs/mongoose';
5 | import { User, UserSchema } from './db/user.schema';
6 |
7 | @Module({
8 | imports: [
9 | MongooseModule.forFeature([{
10 | name: User.name, schema: UserSchema
11 | }])],
12 | providers: [UsersService],
13 | controllers: [UsersController]
14 | })
15 | export class UsersModule { }
16 |
--------------------------------------------------------------------------------
/src/users/users.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { UsersRepository } from './db/user.repository';
3 | import { User } from './db/user.schema';
4 | import { ObjectId } from 'mongodb';
5 | import { UpdateUserDto } from './dto';
6 |
7 | @Injectable()
8 | export class UsersService {
9 | constructor(
10 | private readonly usersRepository: UsersRepository
11 | ) { }
12 |
13 | async getUserById(
14 | userId: string
15 | ): Promise {
16 | return this.usersRepository.findOne({ userId })
17 | }
18 |
19 | async getUsers(): Promise {
20 | return this.usersRepository.find({});
21 | }
22 |
23 | async createUser(
24 | email: string,
25 | age: number
26 | ): Promise {
27 | return this.usersRepository.create({
28 | userId: new ObjectId().toHexString(),
29 | email,
30 | age,
31 | favoriteFoods: []
32 | })
33 | }
34 |
35 | async updateUser(
36 | userId: string,
37 | userUpdates: UpdateUserDto
38 | ): Promise {
39 | return this.usersRepository.findOneAndUpdate({ userId }, userUpdates);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": [
3 | "js",
4 | "json",
5 | "ts"
6 | ],
7 | "rootDir": ".",
8 | "testEnvironment": "node",
9 | "testRegex": ".e2e-spec.ts$",
10 | "transform": {
11 | "^.+\\.(t|j)s$": "ts-jest"
12 | },
13 | "moduleNameMapper": {
14 | "@app/common/(.*)": "/../libs/common/src/$1",
15 | "@app/common": "/../libs/common/src"
16 | }
17 | }
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "ES2021",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "skipLibCheck": true,
15 | "strictNullChecks": false,
16 | "noImplicitAny": false,
17 | "strictBindCallApply": false,
18 | "forceConsistentCasingInFileNames": false,
19 | "noFallthroughCasesInSwitch": false,
20 | "paths": {
21 | "@app/common": [
22 | "libs/common/src"
23 | ],
24 | "@app/common/*": [
25 | "libs/common/src/*"
26 | ]
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------