--- title: "Writing Unit Tests" description: "Overview to Unit Testing" --- ## Unit Tests - All individual unit tests are independent of each other. We don't use any shared state between tests. - Test environment includes `sakila` sample database and any change to it by a test is reverted before running other tests. - While running unit tests, it tries to connect to mysql server running on `localhost:3306` with username `root` and password `password` (which can be configured) and if not found, it will use `sqlite` as a fallback, hence no requirement of any sql server to run tests. ### Pre-requisites - MySQL is preferred - however tests can fallback on SQLite too ### Setup ```bash pnpm --filter=nocodb install # add a .env file cp tests/unit/.env.sample tests/unit/.env # open .env file open tests/unit/.env ``` Configure the following variables > DB_HOST : host > DB_PORT : port > DB_USER : username > DB_PASSWORD : password ### Run Tests ``` bash pnpm run test:unit ``` ### Folder Structure The root folder for unit tests is `packages/nocodb/tests/unit` - `rest` folder contains all the test suites for rest apis. - `model` folder contains all the test suites for models. - `factory` folder contains all the helper functions to create test data. - `init` folder contains helper functions to configure test environment. - `index.test.ts` is the root test suite file which imports all the test suites. - `TestDbMngr.ts` is a helper class to manage test databases (i.e. creating, dropping, etc.). ### Factory Pattern - Use factories for create/update/delete data. No data should be directly create/updated/deleted in the test. - While writing a factory make sure that it can be used with as less parameters as possible and use default values for other parameters. - Use named parameters for factories. ```ts createUser({ email, password}) ``` - Use one file per factory. ### Walk through of writing a Unit Test We will create an `Table` test suite as an example. #### Configure test We will configure `beforeEach` which is called before each test is executed. We will use `init` function from `nocodb/packages/nocodb/tests/unit/init/index.ts`, which is a helper function which configures the test environment(i.e resetting state, etc.). `init` does the following things - - It initializes a `Noco` instance(reused in all tests). - Restores `meta` and `sakila` database to its initial state. - Creates the root user. - Returns `context` which has `auth token` for the created user, node server instance(`app`), and `dbConfig`. We will use `createProject` and `createProject` factories to create a project and a table. ```typescript let context; beforeEach(async function () { context = await init(); project = await createProject(context); table = await createTable(context, project); }); ``` #### Test case We will use `it` function to create a test case. We will use `supertest` to make a request to the server. We use `expect`(`chai`) to assert the response. ```typescript it('Get table list', async function () { const response = await request(context.app) .get(`/api/v1/db/meta/projects/${project.id}/tables`) .set('xc-auth', context.token) .send({}) .expect(200); expect(response.body.list).to.be.an('array').not.empty; }); ``` :::info We can also run individual test by using `.only` in `describe` or `it` function and the running the test command. ::: ```typescript it.only('Get table list', async () => { ``` #### Integrating the New Test Suite We create a new file `table.test.ts` in `packages/nocodb/tests/unit/rest/tests` directory. ```typescript import 'mocha'; import request from 'supertest'; import init from '../../init'; import { createTable, getAllTables } from '../../factory/table'; import { createProject } from '../../factory/project'; import { defaultColumns } from '../../factory/column'; import Model from '../../../../src/lib/models/Model'; import { expect } from 'chai'; function tableTest() { let context; let project; let table; beforeEach(async function () { context = await init(); project = await createProject(context); table = await createTable(context, project); }); it('Get table list', async function () { const response = await request(context.app) .get(`/api/v1/db/meta/projects/${project.id}/tables`) .set('xc-auth', context.token) .send({}) .expect(200); expect(response.body.list).to.be.an('array').not.empty; }); } export default function () { describe('Table', tableTests); } ``` We can then import the `Table` test suite to `Rest` test suite in `packages/nocodb/tests/unit/rest/index.test.ts` file(`Rest` test suite is imported in the root test suite file which is `packages/nocodb/tests/unit/index.test.ts`). ### Seeding Sample DB (Sakila) ```typescript function tableTest() { let context; let sakilaProject: Project; let customerTable: Model; beforeEach(async function () { context = await init(); /******* Start : Seeding sample database **********/ sakilaProject = await createSakilaProject(context); /******* End : Seeding sample database **********/ customerTable = await getTable({project: sakilaProject, name: 'customer'}) }); it('Get table data list', async function () { const response = await request(context.app) .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}`) .set('xc-auth', context.token) .send({}) .expect(200); expect(response.body.list[0]['FirstName']).to.equal('MARY'); }); } ```