Skip to content

Unit Test

Vona strongly recommends test-driven development. Test-driven development can effectively accumulate development results and identify potential problems as soon as possible when code changes occur, thereby significantly improving the robustness of the code

Vona uses a parallel mechanism to run test files, thereby significantly improving test efficiency

WARNING

Sqlite3 only supports one write connection, so parallel mechanism is not supported

Create test file

1. Cli command

bash
$ vona :create:test student --module=demo-student

2. Menu command

TIP

Context menu - [Module Path]: Vona Create/Test

Execute unit tests

bash
$ npm run test

When this command is executed, the system will perform the following steps:

  1. Create a global app object
  2. Clean up Redis data
  3. Delete the old database and recreate a new database
  4. Execute migration code
  5. Parallel execute unit test files
bash
$ npm run db:reset

Unlike the test command, db:reset only executes the first four steps and does not execute the unit test files

Test coverage

bash
$ npm run cov

Example

1. Simulate the request context object

typescript
describe('student.test.ts', () => {
  it('action:student', async () => {
    await app.bean.executor.mockCtx(async () => {
      // do something
    });
  });
});
  • Specify the current language for ctx
typescript
describe('student.test.ts', () => {
  it('action:student', async () => {
    await app.bean.executor.mockCtx(async () => {
      // do something
    }, { locale: 'zh-cn' });
  });
});

2. Get the module Scope object

typescript
await app.bean.executor.mockCtx(async () => {
  const scopeStudent = app.scope('demo-student');
});

3. Use Service

typescript
const scopeStudent = app.scope('demo-student');
const students = await scopeStudent.service.student.findMany();

4. Use Model

typescript
const scopeStudent = app.scope('demo-student');
const students = await scopeStudent.model.student.select();

5. Using Entity

typescript
const scopeStudent = app.scope('demo-student');
const tableName = scopeStudent.entity.student.$table;
const fieldName = scopeStudent.entity.student.name;

6. Access API to test Controller

typescript
await app.bean.executor.mockCtx(async () => {
  const students = await app.bean.executor.performAction('get', '/demo/student');
});

7. Simulate authentication

  • Simulate login
typescript
await app.bean.executor.mockCtx(async () => {
await app.bean.passport.signinMock();
});

You can specify the login username, the default is admin

typescript
await app.bean.passport.signinMock('admin');
  • Get current user information
typescript
await app.bean.passport.signinMock();

const passport = app.ctx.passport;
const user = app.ctx.user;

const passport = app.bean.passport.current;
const user = app.bean.passport.currentUser;
const auth = app.bean.passport.currentAuth;
const roles = app.bean.passport.currentRoles;
  • Simulate logout
typescript
await app.bean.passport.signout();

8. Tools: assert

Vona uses Node's built-in assertion library

typescript
import assert from 'node:assert';

await app.bean.executor.mockCtx(async () => {
  assert.equal(app.config.server.listen.port, 7102);
});

9. Tool: catchError

catchError can capture error exception more elegantly

  • General writing
typescript
await app.bean.executor.mockCtx(async () => {
  const scopeStudent = app.scope('demo-student');
  try {
    const students = await scopeStudent.service.student.findMany();
  } catch (err) {
    // do something
  }
});
  • Use catchError
typescript
import { catchError } from '@cabloy/utils';

await app.bean.executor.mockCtx(async () => {
  const scopeStudent = app.scope('demo-student');
  const [students, err] = await catchError(() => {
    return scopeStudent.service.student.findMany();
  });
  if (err) {
    // do somthing on err
  }
});

10. Complete Crud test

typescript
describe('student.test.ts', () => {
  it('action:student', async () => {
    await app.bean.executor.mockCtx(async () => {
      // data
      const data: DtoStudentCreate = {
        name: '__Tom__',
        description: 'This is a test',
      };
      const dataUpdate: DtoStudentUpdate = {
        name: '__TomNew__',
        description: 'This is a test',
      };
      // login
      await app.bean.passport.signinMock();
      // create
      const studentId = await app.bean.executor.performAction('post', '/demo/student', { body: data });
      assert.equal(!!studentId, true);
      // findMany
      const queryRes: DtoStudentQueryRes = await app.bean.executor.performAction('get', '/demo/student');
      assert.equal(queryRes.list.findIndex(item => item.name === data.name) > -1, true);
      // update
      await app.bean.executor.performAction('patch', '/demo/student/:id', {
        params: { id: studentId },
        body: dataUpdate,
      });
      // findOne
      let student: EntityStudent = await app.bean.executor.performAction('get', '/demo/student/:id', { params: { id: studentId } });
      assert.equal(student.name, dataUpdate.name);
      // delete
      await app.bean.executor.performAction('delete', '/demo/student/:id', { params: { id: student.id } });
      // findOne
      student = await app.bean.executor.performAction('get', '/demo/student/:id', { params: { id: student.id } });
      assert.equal(student, undefined);
      // logout
      await app.bean.passport.signout();
    });
  });
});

Released under the MIT License.