E2E Testing with ExpressoTS and SuperTest
Tags: typescript, javascript, qa, tutorial
Published: 2024-04-26 08:53:53

## Intro
Supertest is a popular npm library commonly used for testing HTTP servers and APIs. It works seamlessly with Node.js HTTP servers, including those created with frameworks like Express. Supertest provides a high-level abstraction for testing HTTP, allowing you to make requests to your application and assert the responses.

## Purpose
Supertest is designed to simplify the process of sending simulated HTTP requests to a server and verifying the responses. It is primarily used in the context of writing tests, often in combination with testing frameworks like Jest, Mocha, or Jasmine. The library's main purpose is to enable developers to write concise tests for their APIs, ensuring that the endpoints function as expected under various conditions.

## Pros
- Ease of Use: Supertest provides a fluent, chainable API that makes it easy to write readable and expressive tests.
- Integration: It integrates smoothly with popular testing frameworks like Mocha, Jest, and others, allowing you to use familiar assertion styles and test management features.
- Flexibility: You can test both local and remote servers, making it useful for a variety of testing scenarios including integration and acceptance testing.
- No Server Startup Required: When testing Node.js HTTP servers (like Express apps), Supertest can directly invoke the server's request handling code without needing to listen on a network port. This makes tests faster and easier to manage in a development environment.
- Response Assertions: Supertest supports a wide range of HTTP assertions, such as checking status codes, header fields, and response bodies, which helps in validating API responses thoroughly.

## Cons
- Limited to HTTP: It is designed specifically for HTTP API testing. If your application logic is not exposed over HTTP or requires more complex interaction (like WebSockets or other protocols), Supertest will not be suitable.
- Asynchronous Complexity: Like any tool that performs I/O operations, tests written with Supertest are inherently asynchronous. This can complicate test code, especially for beginners or in complex scenarios requiring multiple asynchronous steps.
- Error Handling: Error handling in asynchronous tests can be tricky, and Supertest’s error messages can sometimes be vague, which might complicate debugging efforts.
- Environment Mocking: While Supertest is great for testing the real responses from your server, you might still need to set up and tear down test environments or mock external services, which could require additional tools or libraries.

## Integration with ExpressoTS

To help you on developing your E2E testing to verify your endpoints we fully integrated ExpressoTS Application with Supertest, making it easy to setup the test and validate the entire process from application bootstrap to make a http method request call to your endpoint and validate the response back.

Here is an example:

ExpressoTS application bootstrap

```ts
async function bootstrap() {
    const app = await AppFactory.create(container, App);
    app.listen(3000, ServerEnvironment.Development);
}
```

Endpoint for testing

```ts
@controller("/organization")
export class OrganizationController {
    @Post("")
    async create(
        @body() organization: IOrganizationRequestDto,
        @response() res: express.Response,
    ) {
        return res.status(StatusCode.Created).json(organization);
    }
}
```

The Test Setup

```ts
import "reflect-metadata";

import { ServerEnvironment } from "@expressots/adapter-express";
import { AppFactory, StatusCode } from "@expressots/core";
import express from "express";
import request from "supertest";
import { container } from "../src/app.container";
import { App } from "./../src/app.provider";

const appExpress = express();

describe("api test", () => {
    let server: express.Application;

    beforeAll(async () => {
        const appInstance = await AppFactory.create(container, App);
        await appInstance.listen(3000, ServerEnvironment.Development);

        server = await appInstance.getHttpServer();
    });

    describe("organization.module", () => {
        test("POST /organization", async () => {
            const newOrg = {
                name: "test org",
            };

            const response = await request(server)
                .post("/organization")
                .send(newOrg);

            expect(response.status).toBe(StatusCode.Created);
            expect(response.body).toEqual(newOrg);
        });
    });
});
```

Results

![E2E Testing results image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hpjs4yuo8jk54cj6ts1.png)

Happy coding!