[Nestia] I made automatic e2e test functions generator for NestJS
Tags: typescript, javascript, opensource, nestjs
Published: 2023-05-06 08:04:55

## Outline
Automatic E2E (End-to-End) test functions generator for NestJS

I made automatic e2e test functions generator for NestJS, corresponding to every API endpoints. Just setup [nestia](https://github.com/samchon/nestia) following the guide documents, and run `npx nestia e2e` comnand, then you can get the automatically generated e2e functions.

Also, currently [nestia](https://github.com/samchon/nestia) supports automatic SDK and e2e test functions generator only for NestJS. However, at next month, every langagues and every frameworks would be supported.

  - Guide Documents: https://nestia.io/docs/sdk/e2e/
  - Github Repository: https://github.com/samchon/nestia

```typescript
import typia, { Primitive } from "typia";

import api from "../../../../src/api";
import type { IBbsArticle } from "../../../../src/api/structures/IBbsArticle";

export const test_api_bbs_articles_store = async (
    connection: api.IConnection,
): Promise<void> => {
    const output: Primitive<IBbsArticle> =
        await api.functional.bbs.articles.store(
            connection,
            typia.random<Primitive<string>>(),
            typia.random<Primitive<IBbsArticle.IStore>>(),
        );
    typia.assert(output);
};
```

----------------------------

## Principles
The secret of automatic `e2e` test functions generation is, just analyzing your NestJS backend server codes in the compilation level. 

When such NestJS backend server code comes:

```typescript
import { Controller } from "@nestjs/common";
import typia from "typia";

import core from "@nestia/core";

import { IBbsArticle } from "@api/lib/structures/IBbsArticle";

@Controller("/bbs/articles/:section")
export class BbsArticlesController {
    /**
     * Store a new article.
     *
     * @param section Section code
     * @param input Content to store
     * @returns Newly archived article
     */
    @core.TypedRoute.Post()
    public async store(
        @core.TypedParam("section") section: string,
        @core.TypedBody() input: IBbsArticle.IStore,
    ): Promise<IBbsArticle> {
        return {
            ...typia.random<IBbsArticle>(),
            section,
            ...input,
        };
    }

    /**
     * Update an article.
     *
     * @param section Section code
     * @param id Target article ID
     * @param input Content to update
     * @returns Updated content
     */
    @core.TypedRoute.Put(":id")
    public async update(
        @core.TypedParam("section") section: string,
        @core.TypedParam("id", "uuid") id: string,
        @core.TypedBody() input: IBbsArticle.IStore,
    ): Promise<IBbsArticle> {
        return {
            ...typia.random<IBbsArticle>(),
            id,
            section,
            ...input,
        };
    }
}
```

[Nestia](https://github.com/samchon/nestia) analyzes above code, and generates an SDK library. 

The SDK (Software Development Kit) library is composed with a set of `fetch` functions interacting with the NestJS server. It makes client (maybe frontend) developers to easily interact with the backend server by auto-completion and type safety.

```typescript
/**
 * @packageDocumentation
 * @module api.functional.bbs.articles
 * @nestia Generated by Nestia - https://github.com/samchon/nestia 
 */
//================================================================
import { Fetcher, Primitive } from "@nestia/fetcher";
import type { IConnection } from "@nestia/fetcher";

import type { IBbsArticle } from "./../../../structures/IBbsArticle";

/**
 * Store a new article.
 * 
 * @param connection connection Information of the remote HTTP(s) server with headers (+encryption password)
 * @param section Section code
 * @param input Content to store
 * @returns Newly archived article
 * 
 * @controller BbsArticlesController.store()
 * @path POST /bbs/articles/:section
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export function store
    (
        connection: IConnection,
        section: string,
        input: Primitive<store.Input>
    ): Promise<store.Output>
{
    return Fetcher.fetch
    (
        connection,
        store.ENCRYPTED,
        store.METHOD,
        store.path(section),
        input
    );
}
export namespace store
{
    export type Input = Primitive<IBbsArticle.IStore>;
    export type Output = Primitive<IBbsArticle>;

    export const METHOD = "POST" as const;
    export const PATH: string = "/bbs/articles/:section";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: false,
        response: false,
    };

    export function path(section: string): string
    {
        return `/bbs/articles/${encodeURIComponent(section ?? "null")}`;
    }
}

/**
 * Update an article.
 * 
 * @param connection connection Information of the remote HTTP(s) server with headers (+encryption password)
 * @param section Section code
 * @param id Target article ID
 * @param input Content to update
 * @returns Updated content
 * 
 * @controller BbsArticlesController.update()
 * @path PUT /bbs/articles/:section/:id
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export function update
    (
        connection: IConnection,
        section: string,
        id: string,
        input: Primitive<update.Input>
    ): Promise<update.Output>
{
    return Fetcher.fetch
    (
        connection,
        update.ENCRYPTED,
        update.METHOD,
        update.path(section, id),
        input
    );
}
export namespace update
{
    export type Input = Primitive<IBbsArticle.IStore>;
    export type Output = Primitive<IBbsArticle>;

    export const METHOD = "PUT" as const;
    export const PATH: string = "/bbs/articles/:section/:id";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: false,
        response: false,
    };

    export function path(section: string, id: string): string
    {
        return `/bbs/articles/${encodeURIComponent(section ?? "null")}/${encodeURIComponent(id ?? "null")}`;
    }
}
```

At last, [nestia](https://github.com/samchon/nestia) generates E2E test functions utilizing the SDK library. 

For reference, automatically generated e2e functions compose parameter values through [`typia.random<T>()`](https://typia.io/docs/random/) function like example below. Therefore, you need to customize those E2E test functions, to be suitable for your domain logics.

```typescript
import typia, { Primitive } from "typia";

import api from "./../../../../api";
import type { IBbsArticle } from "./../../../../api/structures/IBbsArticle";

export const test_api_bbs_articles_store = async (
    connection: api.IConnection
): Promise<void> => {
    const output: Primitive<IBbsArticle> = 
        await api.functional.bbs.articles.store(
            connection,
            typia.random<Primitive<string>>(),
            typia.random<Primitive<IBbsArticle.IStore>>(),
        );
    typia.assert(output);
};

export const test_api_bbs_articles_update = async (
    connection: api.IConnection
): Promise<void> => {
    const output: Primitive<IBbsArticle> = 
        await api.functional.bbs.articles.update(
            connection,
            typia.random<Primitive<string>>(),
            uuid(),
            typia.random<Primitive<IBbsArticle.IStore>>(),
        );
    typia.assert(output);
};

const uuid = (): string =>
    "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
        const r = (Math.random() * 16) | 0;
        const v = c === "x" ? r : (r & 0x3) | 0x8;
        return v.toString(16);
    });
```

---------------------------

## What [nestia](https://github.com/samchon/nestia) is?

![Nestia Logo](https://nestia.io/logo.png)

A set of helper libraries for NestJS.

  - `@nestia/core`: super-fast decorators
  - `@nestia/sdk`:
    - SDK generator for clients
    - Swagger generator evolved than ever
    - Automatic E2E test functions generator
  - `nestia`: just CLI (command line interface) tool

> **Note**
> 
> - **Only one line** required, with pure TypeScript type
> - Runtime validator is **20,000x faster** than `class-validator`
> - JSON serialization is **200x faster** than `class-transformer`
> - SDK is similar with [tRPC](https://trpc.io), but much advanced

![nestia-sdk-demo](https://user-images.githubusercontent.com/13158709/215004990-368c589d-7101-404e-b81b-fbc936382f05.gif)

> Left is server code, and right is e2e test function utilizing SDK

[Nestia](https://github.com/samchon/nestia) is one of my open source library which can boost up NestJS performance and productivity. One thing interesting is, all of those benefits are coming from a point: [nestia](https://github.com/samchon/nestia) supports pure TypeScript type.

In normal case, NestJS forces user to define triple redundant DTO schemas. The 1st is pure TypeScript type, the 2nd is `class-validator` decorator, and the last (3rd) is `@nestjs/swagger` decorator. 

You may understand how it annoying and time-consuming, even you haven't used NestJS at all, just by looking below code.

```typescript
export class BbsArticle {
    @IsString()
    @ApiProperty({
        format: "uuid",
    })
    id!: string;

    // DUPLICATED SCHEMA DEFINITION
    // - duplicated function call + property type
    // - have to specify `isArray` and `nullable` props by yourself
    @IsArray()
    @IsObject()
    @ValidateNested()
    @Type(() => AttachmentFile)
    @ApiProperty({
        type: () => AttachmentFile,
        nullable: true,
        isArray: true,
        description: "List of attached files.",
    })
    files!: AttachmentFile[] | null;

    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        minLength: 5,
        maxLength: 100,
        description: "Title of the article.",
    })
    title!: string | null;

    @IsString()
    @ApiProperty({
        description: "Main content body of the article."
    })
    body!: string;

    @IsString()
    @ApiProperty({
        format: "date-time",
        description: "Creation time of article",
    })
    created_at!: string;
}

export class AttachmentFile {
    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        maxLength: 255,
        pattern: "^[a-zA-Z0-9-_]+$",
        description: "File name.",
    })
    name!: string | null;

    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        maxLength: 255,
        pattern: "^[a-zA-Z0-9-_]+$",
        description: "File extension.",
    })
    extension!: string | null;

    @IsString()
    @ApiProperty({
        format: "url",
        description: "URL of the file.",
    })
    url!: string;
}
```

Besides, [nestia](https://github.com/samchon/nestia) needs only pure TypeScript type. 

If you're wondering how [nestia](https://github.com/samchon/nestia) can do that, please read my previous article - [[Nestia] Boost up NestJS server much faster and easier (maximum 20,000x faster + tRPC similar)](https://dev.to/samchon/nestia-boost-up-your-nestjs-server-much-faster-and-easier-maximum-20000x-faster-59o5).

```typescript
export interface IBbsArticle {
    /**
     * Primary Key.
     * 
     * @format uuid
     */
    id: string;

    /**
     * List of attached files.
     */
    files: IAttachmentFile[] | null;

    /**
     * Title of the article.
     * 
     * @minLength 5
     * @maxLength 100
     */
    title: string | null;

    /**
     * Main content body of the article.
     */
    body: string;

    /**
     * Creation time of article.
     * 
     * @format date-time
     */
    created_at: string;
}

export interface IAttachmentFile {
    /**
     * File name.
     * 
     * @pattern ^[a-z0-9]+$
     * @maxLength 255
     */
    name: string | null;

    /**
     * File extension.
     * 
     * @pattern ^[a-z0-9]+$
     * @maxLength 8
     */
    extension: string | null;

    /**
     * URL of the file.
     * 
     * @format uri
     */
    url: string;
}
```

Someone said me that this is the most important benefit of [nestia](https://github.com/samchon/nestia), even rather than boosting up performance enourmously. He said that, it makes [nestia](https://github.com/samchon/nestia) to be "no reason not to use when developing NestJS".

I really like his word "no reason not to use". Do you agree?

----------------------------

## Next Episode
In nowadays, I'm developing migration project from Swagger to NestJS. 

If the project being done, you can create a NestJS project from any `swagger.json` file. Therefore, what language and framework ever you use, you also can automatically generate SDK library and E2E test functions for every API endpoints.

Be looking forward to it. I'll come back with at next month.

> Also preparing automatic frontend application generator from NestJS server code. Its name is `reactia`, and may possible to release in this year.
>
> In means that, with the Swagger to NestJS migration project, you can easily create a full-stack application just by taking a `swagger.json` file. 
> 
> Be looking forward to them. I'll show you a new world.