Skip to content

apparts-js/apparts-backend-test

Repository files navigation

@apparts/backend-test

This library supports testing @apparts-based backends that use Postgresql databases provide express based APIs.

Usage

  1. Add to your package.json in the scripts section:
    "testOne": "jest",
    "test": "jest --watch --detectOpenHandles",
    "testCoverage": "jest --coverage"
        
  2. Create a jest.config.js file in the root directory of your project.
    const jestConfig = require("@apparts/backend-test").getJestConfig();
    
    module.exports = {
      ...jestConfig,
      // additional config
    };
        
  3. Create a config/db-test-config.json or config/db-test-config.js or export the values as an environment variable with the name DB_TEST_CONFIG with this content (as JSON, for the js file you’d need to export the object):
    {
      "use": "postgresql",
      "postgresql": {
        "host": "localhost",
        "port": 5432,
        "user": "postgres",
        "pw": "",
        "db": "<dbname>",
        "maxPoolSize": 1,
        "connectionTimeoutMillis": 10000,
        "idleTimeoutMillis": 10000,
        "bigIntAsNumber": true
      }
    }
        
  4. Create tests. To use this library, start the tests with this on top:
    const { app, url, error, getPool } = require("@apparts/backend-test")({
      testName: "<testname>",
      // apiContainer: myEndpoint,
      ...require("./tests/config.js")  
    });
        
  5. Create a tests/config.js for storing test information that is valid for more than one test:
    const fs = require("fs");
    module.exports = {
      schemas: ["schema-file-name-0001.sql" /*, ...*/]
        .map(name => fs.readFileSync(name).toString()),
      apiVersion: 1,
    };
        
  6. Run tests with
    npm run test
    
    # for coverage report:
    npm run testCoverage
    
    # without watching file changes:
    npm run testOne
        

Parameters

require("@apparts/backend-test") returns a function with the following parameters:

testName : string
(Required) The name of this particular test (needs to be unique). Used to create a database for this test to run in
apiVersion : int
(Required) The API version of a versioned API, that is being tested
apiContainer: object
The container for the functions you want @apparts/types checkApiTypes to test against. If this parameter is not supplied, checkType and allChecked will throw an error.
schemas : [string]
Database schemas, to be run first to create all tables
databasePreparations : [async function : string]
A list of functions (that can be async) that return strings, containing sql code that should be run to setup the database
testApp : express app
If supplied, this app will be used as the express app, the tests will be performed on. Otherwise a default test app will be used.

Returns

require("@apparts/backend-test") returns a function wich returns an object with the following key/value pairs:

checkType : (any, string) => boolean
A wrapper around the checkType function from the @apparts/types package. The first parameter of checkType is already set (with value from apiContainer parameter) for convenience.
allChecked : (string) => boolean
A wrapper around the ==allChecked= function from the @apparts/types package. The first parameter of allChecked is already set (with value from apiContainer parameter) for convenience.
app : <express app>
A express app. It lacks routes, these have to be added manually. A minimal middleware is already applied to this app (injection of database, body parser).
url : (string) => string
A helper that turns a string to a versioned version of this string, by prepending /v/<apiVersion>/ to the string. apiVersion is the value of the parameter apiVersion. This allows to change the api-version of all calls with one variable.
error : (string, ?string) => object
A helper that creates an object of the form of the body, produced by the HttpError from the package @apparts/error. Can be used with the supertest toMatchObject matcher.
runDBQuery : async (async (<dbs>) => any) => any
A function that expects a function as parameter. This function receives a @apparts/db DBS object as parameter. It’s return value will be awaited and returned by runDBQuery.
getPool : () => <dbs>
Returns a @apparts/db DBS object.

Minimal example

  • jest.config.js:
    const jestConfig = require("@apparts/backend-test").getJestConfig();
    
    module.exports = {
      ...jestConfig,
      // additional config
    };
        
  • config/db-test-config.json as described above
  • Tests with
    const { app, url } = require("@apparts/backend-test")({
      testName: "<testname>",
      apiVersion: 1
    });
    
    test("My test", async () => {
      // requesting GET "/v/1/test"
      const response = await request(app).get(url("test"));
      expect(response.status).toBe(200);
    });
        

Full-ish example

const {
  app,
  url,
  checkType,
  allChecked,
  error,
  getPool,
} = require("@apparts/backend-test")({
  testName: "<testname>",
  apiContainer: require("./myEndpoint"),

  // Returns everything that is the same for all endpoints of this
  // APIs version: apiVersion, schemas
  ...require("./tests/config.json") ,

  // Insert values for the tests to use.
  databasePreparations: [
    // Common setup queries can be stored in a file
    require("./tests/insertUsers.sql.js"),
    // Simple insertations
    () => 'INSERT INTO "myTable" (myCollumn) VALUES (1), (2)';
    // More complicated calculated values
    async () => {
      const hash = await require("bcryptjs").hash("password123", 10);
      return `INSERT INTO "passwords" (password) VALUES (${hash})`;
    };
  ],
});

const request = require("supertest");

describe("GET test", () => {
  // Using a variable for the function name makes it easy to copy this
  // test for another endpoint and not forgot to change the function
  // name in some places.
  const functionName = "myEndpoint";
  test("Check return code", async () => {
    // Requesting GET "/v/1/test", using the url function. This makes
    // it easy to copy this file, edit the tests to reflect api changes
    // and thus reuse it for the next api version.
    const response = await request(app).get(url("test"));
    expect(response.status).toBe(200);

    // Checking against the database
    // const dbs = getPool();
    // await dbs.raw("SELECT ...");
    // expect(...);

    // Throws if not correct, so no expect is needed
    checkType(response, functionName);
  });

  test("Check error", async () => {
    const response = await request(app).get(url("test/error"));
    expect(response.status).toBe(400);
    expect(response.body).toMatchObject(error("This endpoint fails", "Reason: \"error\""));
    checkType(response, functionName);
  });

  test(("All possible responses tested") => {
    // Throws if not all checked, so no expect is needed
    allChecked(functionName);
  });
});

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published