Skip to main content

Testing

We have 3 types of test, unit, integration and automated API tests. Developers are responsible for writing unit and integration tests and working with QA team to assist with automated tests where necessary.

Unit Tests


We are using Jest for our unit tests. Each module or components should contain a folder called tests where all unit test for that module / component live.

To run unit tests for a given module or component you can run the following

bit test components/context

To run all unit tests (bear in mind this will also run the integration tests as components/test is a bit managed component)

bit test

It's also worth pointing out that the following commands will trigger all tests to be run

bit build
bit tag

See the bottom of this document for a way to watch specific tests and keep running them when the underlying source code changes without having to run all tests in a component

Integration Tests

Integration tests live in the tests namespace, tehre is a component per module and a utils component, we should not create integration tests in specific modules / components folders. This keeps all integration tests together and gives us more control over when we run them as they will be more time consuming to execute.

We also use Jest for integration tests.

Each developer and environment has a dedicated integration test database that can be re-seeded at any time and should be re-seeded prior to each test run

Seeding your test database

To reset your test database you first need to ensure you have the following key in your config overrides file. If you have not set up your local overrides, please read about how to do this in the configuration docs

{
"ravendb.testDatabasePrefix": "[YOUR_NAME]"
}

You can seed your integration test database with the following pnpm command from package.json

pnpm run db:test

This will drop and recreate a new database specific to you based on your test database prefix from above and seed it with data exported from the test components, see tests/inventory/seeddata for an example of how we generate and export test data for modules.

Seed data for each module should not be modified as part of the test process as this will potentially create problems for other tests. In general this should be reference data / static data such as asset categories that are often needed but can safely be shared by all tests.

As part of the creation of the database all indexes for each service being tested will also be created and validated as part of this process.

Test Driver

The utils folder in tests/utils contains resources to assist with testing. The main helper is the TestDriver class. The TestDriver should be initialised with one or more manifests from the module you want to test (usually one as we will mostly be testing one module at a time).

import { manifest } from '@hectare/platform.modules.inventory.api'

let driver: TestDriver = null

beforeAll(() => {
driver = new TestDriver(manifest)
})

If you need to create test flows which cover multiple modules you should create a driver passing in an array of modules the driver needs to support for the tests.

When you initialise the driver with a manifest, the OpenAPI schemas for the module being tested will automatically be validated and any errors will be reported when you run the tests, schema validation errors will result in test failures.

The driver can then be used to invoke an api, for example

API

const event = EventBuilder.http_post('/inventory/assets')
.withPayload({
name: 'Wheat',
location: 'Storage',
organisation_id: '1-A',
asset_types: [1, 2, 3],
tags: ['farm-a', 'august_harvest'],
weight_kg: 2500,
volume_cubic_m: 10
})
.done()

const create_ctx = await driver.executeApi(event)
expect(create_ctx.event.response.statusCode).toBe(200)

In the API example above you create your event payload and trigger the executeApi method on the driver, executeApi will locate the appropriate handler based on the API path, validate the incoming payload and execute the handler.

The EventBuilder is a simple utility for helping create events

Registering & Testing Event Handlers

Integration tests use a mock event bus for publishing events, we can use this mock event bus to register handlers in order to be able to test events were fired and to validate their payload. We cant execute expectations in the handler as this runs outside the test context and failures will not cause the test to fail. We can however capture the event payload from the event handler we register and run our expectations after the event is fired to validate it, example below.

// register an event handler so we can test the record created event is fired
let eventPayload: RecordCreated = null
driver.registerEventHandler('RecordCreated', async (ctx): Promise<void> => {
eventPayload = ctx.event.payload as RecordCreated
})

// execute the API which triggers the event
await driver.executeApi(event)

// validate the payload after the API call returns
expect(eventPayload.name).toBe('Wheat')
expect(eventPayload.location).toBe('Storage')

Running Tests

To run integration tests

pnpm run db:test // prepare a clean database for the test run if necessary
bit test tests/inventory // trigger the test component you want to run tests for or...
bit test // will run test for all components

To debug tests first toggle the 'Auto Attach' feature in vscode to on, then

bit test tests/inventory -d

If you want to watch a specific test thats failing you can use the bit watch feature

bit test tests/inventory --watch

This will run through all tests in the suite, then present you with an option list, choose t then enter the name of the test or test suite you want to watch and bit will keep executing the test everytime the source file changes, this allows you to efficiently keep re-testing one or more test