DEV Community

Cover image for [Nestia] I made automatic e2e test functions generator for NestJS
Jeongho Nam
Jeongho Nam

Posted on • Updated on

[Nestia] I made automatic e2e test functions generator for NestJS

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 following the guide documents, and run npx nestia e2e comnand, then you can get the automatically generated e2e functions.

Also, currently nestia supports automatic SDK and e2e test functions generator only for NestJS. However, at next month, every langagues and every frameworks would be supported.

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);
};
Enter fullscreen mode Exit fullscreen mode

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:

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,
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

/**
 * @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")}`;
    }
}
Enter fullscreen mode Exit fullscreen mode

At last, nestia generates E2E test functions utilizing the SDK library.

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

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);
    });
Enter fullscreen mode Exit fullscreen mode

What nestia is?

Nestia Logo

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, but much advanced

nestia-sdk-demo

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

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 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.

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;
}
Enter fullscreen mode Exit fullscreen mode

Besides, nestia needs only pure TypeScript type.

If you're wondering how nestia can do that, please read my previous article - [Nestia] Boost up NestJS server much faster and easier (maximum 20,000x faster + tRPC similar).

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;
}
Enter fullscreen mode Exit fullscreen mode

Someone said me that this is the most important benefit of nestia, even rather than boosting up performance enourmously. He said that, it makes 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.

Top comments (4)

Collapse
 
pterpmnta profile image
Pedro Pimienta M.

Your post are increidble, but I am noob on Nest Js, Do you have a chanel where to you show how useall these improvements?

Collapse
 
samchon profile image
Jeongho Nam

Maybe you're meaning performance.

Read this issue and follow it, then you can run benchmark on your computer:

github.com/samchon/typia/issues/337

Collapse
 
pterpmnta profile image
Pedro Pimienta M.

Thanks for your answer, but I don't talk just about performance, I talk about video explanation.

Thread Thread
 
samchon profile image
Jeongho Nam

Then visit website, and follow its explanation.

nestia.io/docs