Skip to content

Commit

Permalink
tests: add e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
rubiin committed Nov 25, 2022
1 parent acd74a9 commit 337f6a1
Show file tree
Hide file tree
Showing 89 changed files with 679 additions and 515 deletions.
5 changes: 0 additions & 5 deletions .prettierignore

This file was deleted.

14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@

> ### Blog made using Nestjs + Mikro-orm codebase(backend) containing real world examples (CRUD, auth (password based and oauth), advanced patterns, etc)
[![CI](https://github.com/rubiin/ultimate-nest/actions/workflows/github-ci.yml/badge.svg)](https://github.com/rubiin/ultimate-nest/actions/workflows/github-ci.yml)
<p align="center">
<img alt="GitHub package.json version" src="https://img.shields.io/github/package-json/v/rubiin/ultimate-nest">
<img alt="Workflow test" src="https://github.com/rubiin/ultimate-nest/actions/workflows/github-ci.yml/badge.svg">
<img alt="GitHub" src="https://img.shields.io/github/license/rubiin/ultimate-nest">
<img alt="Lines of code" src="https://img.shields.io/tokei/lines/github/rubiin/ultimate-nest">
</p>
<p align="center">
<a href="https://www.buymeacoffee.com/XbgWxt567" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
</p>
<br/>
NOTE: Starting April 18,2022 , the repo has ditched most promises for observables. You can check the latest promised version code at
[commit](https://github.com/rubiin/ultimate-nest/tree/fb06b34f7d36f36195880e600f8f1b5b86f71213)

> NOTE: Starting April 18,2022 , the repo has ditched most promises for observables. You can check the latest promised version code at [commit](https://github.com/rubiin/ultimate-nest/tree/fb06b34f7d36f36195880e600f8f1b5b86f71213)

# Getting started

Expand Down
65 changes: 65 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Testing Nestjs applications

In the previous posts, I have write a lot of testing codes to verify if our application is working as expected.

Nestjs provides integration with with [Jest](https://github.com/facebook/jest) and [Supertest](https://github.com/visionmedia/supertest) out-of-the-box, and testing harness for unit testing and end-to-end (e2e) test.

## Nestjs test harness

Like the Angular 's `TestBed`, Nestjs provide a similar `Test` facilities to assemble the Nestjs components for your testing codes.

```typescript
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
...
],
}).compile();

service = module.get<UserService>(UserService);
});

```


## End-to-end testing

Nestjs integrates supertest to send a request to the server side.

Use `beforeAll` and `afterAll` to start and stop the application, use `request` to send a http request to the server and assert the response result.
The `APP_URL` is the url of the server, it is defined in the `fixtures/constant.ts` file. This is the base url. Modify it to your own url.
Also since we are not using app module, we need to separately run the server in one terminal and issue the test command in another terminal.
To run the e2e test, use `make test-e2e` command.


```typescript
import * as request from 'supertest';
import { APP_URL } from "../fixtures/constant";
//...

describe('API endpoints testing (e2e)', () => {

const app = APP_URL;

beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
});


// an example of using supertest request.
it("should get a list of all user /posts (GET)", () => {
return request(app)
.get("/posts")
.auth(adminJwtToken, { type: "bearer" })
.expect(({ body }) => {
expect(body.meta).toBeDefined();
expect(body.items).toBeDefined();
})
.expect(200);
});
}
```
More details for the complete e2e tests, check Nestjs 's [test folder](https://github.com/hantsy/nestjs-sample/tree/master/test).
5 changes: 4 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ makemigration:
@NODE_ENV=$(env) npm run orm migration:create

fresh:
@NODE_ENV=$(env) npm run orm migration:fresh --seed
@NODE_ENV=$(env) npm run orm migration:fresh --seed

migrate:
@NODE_ENV=$(env) npm run orm migration:up
Expand Down Expand Up @@ -44,3 +44,6 @@ stop:

remove:
ENV=dev PASSWORD=test@1234 docker-compose -f docker-compose.$(env).yml down

test-e2e:
USER_PASSWORD=Test@1234 NODE_ENV=$(env) yarn test:e2e
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "ultimate-nest",
"version": "1.7.0",
"version": "1.8.0",
"description": "NestJS + MikroORM realworld API example",
"license": "MIT",
"repository": "https://github.com/rubiin/ultimate-nest",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"format": "prettier --write --cache \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"lint": "eslint '{src,test}/**/*.ts' --cache --fix",
"test": "jest",
Expand Down Expand Up @@ -166,10 +166,10 @@
"lint-staged": {
"*.{ts,tsx}": [
"eslint '{src,test}/**/*.ts' --cache --fix",
"prettier --write"
"prettier --cache --write"
],
"*.{json,md,yaml,yml}": [
"prettier --write"
"prettier --cache --write"
]
},
"config": {
Expand Down
45 changes: 21 additions & 24 deletions src/_mocks_/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
import { PageOptionsDto } from "@common/classes/pagination";
import { Order, Roles } from "@common/types/enums";


export const mockedUser = {
idx: 'idx',
username:'username',
password:'password',
firstName:'firstName',
lastName:'lastName',
email:'email',
avatar:'avatar',
roles:[Roles.ADMIN],
mobileNumber:'mobileNumber',
}


idx: "idx",
username: "username",
password: "password",
firstName: "firstName",
lastName: "lastName",
email: "email",
avatar: "avatar",
roles: [Roles.ADMIN],
mobileNumber: "mobileNumber",
};

export const mockedPost = {
slug: 'slug',
title: 'title',
description: 'description',
content: 'content',
tags: ['tag1', 'tag2'],
}
slug: "slug",
title: "title",
description: "description",
content: "content",
tags: ["tag1", "tag2"],
};

export const query: PageOptionsDto = {
page: 1,
limit: 10,
offset: 5,
sort: 'createdAt',
order: Order.DESC
page: 1,
limit: 10,
offset: 5,
sort: "createdAt",
order: Order.DESC,
};
2 changes: 1 addition & 1 deletion src/common/database/base-entity.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PrimaryKey, Property } from "@mikro-orm/core";
import { randomUUID } from "crypto";
import { ApiHideProperty } from "@nestjs/swagger";
import { randomUUID } from "crypto";

/* A base class for all entities. */
export abstract class BaseEntity {
Expand Down
1 change: 1 addition & 0 deletions src/common/database/base.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EntityManager, EntityRepository, FilterQuery, FindOptions, Loaded } from "@mikro-orm/core";

import { BaseEntity } from "./base-entity.entity";

export class BaseRepository<T extends BaseEntity> extends EntityRepository<T> {
Expand Down
2 changes: 1 addition & 1 deletion src/common/database/factories/user.factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Factory, Faker } from "@mikro-orm/seeder";
import { Roles } from "@common/types/enums/permission.enum";
import { User } from "@entities";
import { Factory, Faker } from "@mikro-orm/seeder";

/* `UserFactory` is a factory that creates `User` instances */
export class UserFactory extends Factory<User> {
Expand Down
1 change: 1 addition & 0 deletions src/common/database/seeders/admin.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Roles } from "@common/types/enums/permission.enum";
import type { EntityManager } from "@mikro-orm/core";
import { Seeder } from "@mikro-orm/seeder";

import { UserFactory } from "../factories/user.factory";

/* It creates a user with the email and password specified in the .env file, and gives them the admin
Expand Down
1 change: 1 addition & 0 deletions src/common/database/seeders/database.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EntityManager } from "@mikro-orm/core";
import { Seeder } from "@mikro-orm/seeder";

import { AdminSeeder } from "./admin.seeder";
import { UserSeeder } from "./user.seeder";

Expand Down
3 changes: 2 additions & 1 deletion src/common/database/seeders/user.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EntityManager } from "@mikro-orm/core";
import { Seeder } from "@mikro-orm/seeder";

import { CommentFactory } from "../factories/comment.factory";
import { PostFactory } from "../factories/post.factory";
import { UserFactory } from "../factories/user.factory";
import { CommentFactory } from "../factories/comment.factory";

/* It creates a post, a user, and a comment */
export class UserSeeder extends Seeder {
Expand Down
2 changes: 1 addition & 1 deletion src/common/decorators/api-file.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { applyDecorators, UseInterceptors } from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { ApiConsumes } from "@nestjs/swagger";

export function ApiFile(name: string = "file") {
export function ApiFile(name = "file") {
return applyDecorators(
UseInterceptors(FileInterceptor(name, ImageMulterOption)),
ApiConsumes("multipart/form-data"),
Expand Down
1 change: 1 addition & 0 deletions src/common/decorators/controller.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { applyDecorators, CacheInterceptor, Controller, UseInterceptors } from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { capitalize } from "helper-fns";

import { Auth } from "./auth.decorator";

export function GenericController(name: string, secured = true) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./ApiPaginated";
export * from "./api-file.decorator";
export * from "./ApiPaginated";
export * from "./auth.decorator";
export * from "./controller.decorator";
export * from "./is-string-minmax.decorator";
Expand Down
1 change: 1 addition & 0 deletions src/common/decorators/is-string-minmax.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { applyDecorators } from "@nestjs/common";
import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from "class-validator";
import { i18nValidationMessage } from "nestjs-i18n";

import { Sanitize } from "./sanitize.decorator";

interface IsStringMinMaxDecoratorOptions {
Expand Down
2 changes: 1 addition & 1 deletion src/common/decorators/sanitize.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { applyDecorators } from "@nestjs/common";
import { Transform } from "class-transformer";
import { JSDOM } from "jsdom";
import DOMPurify from "dompurify";
import { JSDOM } from "jsdom";

const window = new JSDOM("").window;
const purify = DOMPurify(window);
Expand Down
2 changes: 1 addition & 1 deletion src/common/filters/all-exception-translatable.filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { I18nService } from "nestjs-i18n";
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common";
import { Response } from "express";
import { I18nService } from "nestjs-i18n";

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
Expand Down
4 changes: 2 additions & 2 deletions src/common/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, UnauthorizedException, ExecutionContext } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
Expand Down
3 changes: 2 additions & 1 deletion src/common/pipes/sharp.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable, PipeTransform } from "@nestjs/common";
import path from "node:path";

import { Injectable, PipeTransform } from "@nestjs/common";
import sharp from "sharp";

/**
Expand Down
2 changes: 0 additions & 2 deletions src/entities/comment.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,4 @@ export class Comment extends BaseEntity {
super();
Object.assign(this, partial);
}


}
6 changes: 3 additions & 3 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// all entities should be exported from this barrel file

export { ActivityLog } from "./activity-log.entity";
export { Comment } from "./comment.entity";
export { OtpLog } from "./otp-log.entity";
export { RefreshToken } from "./refresh-token.entity";
export { Post } from "./post.entity";
export { Protocol } from "./protocol.entity";
export { RefreshToken } from "./refresh-token.entity";
export { User } from "./user.entity";
export { Post } from "./post.entity";
export { Comment } from "./comment.entity";
3 changes: 2 additions & 1 deletion src/entities/otp-log.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Entity, ManyToOne, Property } from "@mikro-orm/core";
import { BaseEntity } from "@common/database/base-entity.entity";
import { Entity, ManyToOne, Property } from "@mikro-orm/core";

import { User } from "./user.entity";

@Entity()
Expand Down
2 changes: 0 additions & 2 deletions src/entities/post.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,4 @@ export class Post extends BaseEntity {
Object.assign(this, partial);
this.slug = slugify(partial.title);
}


}
2 changes: 1 addition & 1 deletion src/entities/protocol.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, Property } from "@mikro-orm/core";
import { BaseEntity } from "@common/database/base-entity.entity";
import { Entity, Property } from "@mikro-orm/core";

@Entity()
export class Protocol extends BaseEntity {
Expand Down
1 change: 1 addition & 0 deletions src/entities/refresh-token.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseEntity } from "@common/database/base-entity.entity";
import { Entity, ManyToOne, Property } from "@mikro-orm/core";

import { User } from "./user.entity";

@Entity()
Expand Down
3 changes: 1 addition & 2 deletions src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
OneToMany,
Property,
Unique,
wrap
wrap,
} from "@mikro-orm/core";
import { hash } from "argon2";

Expand Down Expand Up @@ -87,7 +87,6 @@ export class User extends BaseEntity {
}
}


constructor(data?: Pick<User, "idx">) {
super();
Object.assign(this, data);
Expand Down
1 change: 1 addition & 0 deletions src/entities/user.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EntityRepository } from "@mikro-orm/postgresql";

import { User } from "./user.entity";

export class UserRepository extends EntityRepository<User> {}
1 change: 1 addition & 0 deletions src/lib/cache/cache.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NestConfigModule } from "@lib/config/config.module";
import { CacheModule, Global, Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import redisStore from "cache-manager-redis-store";

import { CacheService } from "./cache.service";

@Global()
Expand Down
1 change: 1 addition & 0 deletions src/lib/cache/cache.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class CacheService {

if (match.length > 0) {
const promiseQueue = [];

for (const keys of match) {
promiseQueue.push(this.cacheManager.del(keys));
}
Expand Down
Loading

0 comments on commit 337f6a1

Please sign in to comment.