Running Tests Inside QuickJS with TestRunner
Tags: javascript, testing, typescript, sandbox
Published: 2024-07-07 13:23:56

The TestRunner is a lightweight testing library that allows you to write and run tests for JavaScript code with ease. It provides a simple interface for defining test suites, tests, and hooks with support for asynchronous code and configurable timeouts. The library also includes Chai's `expect` function for assertions.

## Benefits of Running Tests in a Sandbox

Running tests in a sandbox environment like QuickJS provides several benefits:

- **Isolation**: Each test runs in a separate environment, ensuring no side effects between tests.
- **Security**: A sandboxed environment limits the potential for malicious code to affect the host system.
- **Consistency**: Ensures that tests are run in a controlled environment, leading to more consistent and reliable results.

## Importing the Library

To use the TestRunner library in your tests, you need to import it. You also need to ensure that the result of `runTests` is exported.

```javascript
import 'testRunner'
```

## Writing Tests

Developers should feel comfortable using the TestRunner as it follows the general approach used in the JavaScript testing ecosystem, similar to Mocha or Jasmine.

### Defining a Test Suite

A test suite is defined using the `describe` function. Inside a test suite, you can define multiple tests using the `it` function.

```javascript
describe('Sample Test Suite', () => {
  it('should pass this test', () => {
    expect(true).to.be.true;
  });

  it('should fail this test', () => {
    expect(false).to.be.true;
  });
});
```

### Using Hooks

Hooks are functions that run before or after tests in a suite. The available hooks are:

- `beforeAll`: Runs once before all tests in the suite.
- `afterAll`: Runs once after all tests in the suite.
- `beforeEach`: Runs before each test in the suite.
- `afterEach`: Runs after each test in the suite.

```javascript
describe('Test Suite with Hooks', () => {
  beforeAll(() => {
    console.log('Running beforeAll hook');
  });

  afterAll(() => {
    console.log('Running afterAll hook');
  });

  beforeEach(() => {
    console.log('Running beforeEach hook');
  });

  afterEach(() => {
    console.log('Running afterEach hook');
  });

  it('should pass this test', () => {
    expect(true).to.be.true;
  });
});
```

### Asynchronous Tests and Hooks

The TestRunner supports asynchronous tests and hooks. Simply return a promise or use async/await in your test or hook functions.

```javascript
describe('Asynchronous Test Suite', () => {
  beforeAll(async () => {
    await new Promise(resolve => setTimeout(resolve, 1000));
  });

  it('should pass this async test', async () => {
    const result = await new Promise(resolve => setTimeout(() => resolve(true), 1000));
    expect(result).to.be.true;
  });
});
```

## Using Chai's `expect`

The TestRunner includes Chai's `expect` function for writing assertions in your tests. The `expect` function is part of Chai's BDD (Behavior-Driven Development) interface. You can learn more about Chai's `expect` function and its usage at the [Chai documentation](https://www.chaijs.com/api/bdd/).

### Examples of `expect` Usage

Here are some examples of using Chai's `expect` function in your tests:

```javascript
it('should assert true is true', () => {
  expect(true).to.be.true;
});

it('should assert equality', () => {
  expect(1 + 1).to.equal(2);
});
```

## Running Tests

To run the tests, you need to call `runTests` and export the result. You can specify a custom timeout for tests and hooks by passing the timeout value in milliseconds as an argument to `runTests`.

```javascript
import 'testRunner'

describe('Sample Test Suite', () => {
  it('should pass this test', () => {
    expect(true).to.be.true;
  });

  it('should fail this test', () => {
    expect(false).to.be.true;
  });
});

export default await runTests();
```

### Custom Timeout

You can set a custom timeout for all tests and hooks in the suite by passing the timeout value to `runTests`.

```javascript
import 'testRunner'

describe('Sample Test Suite with Custom Timeout', () => {
  it('should pass this test', () => {
    expect(true).to.be.true;
  });

  it('should fail this test', () => {
    expect(false).to.be.true;
  });
});

export default await runTests(10000); // Set timeout to 10 seconds
```

## Example Usage

Here is an example demonstrating the complete setup and usage of the TestRunner library.

```javascript
import 'testRunner'

describe('Example Test Suite', () => {
  beforeAll(() => {
    console.log('Running global beforeAll hook');
  });

  afterAll(() => {
    console.log('Running global afterAll hook');
  });

  beforeEach(() => {
    console.log('Running global beforeEach hook');
  });

  afterEach(() => {
    console.log('Running global afterEach hook');
  });

  describe('Nested Suite', () => {
    beforeAll(() => {
      console.log('Running nested beforeAll hook');
    });

    it('should pass this test', () => {
      expect(true).to.be.true;
    });

    it('should fail this test', () => {
      expect(false).to.be.true;
    });
  });

  it('should pass this test outside nested suite', () => {
    expect(1 + 1).to.equal(2);
  });
});

export default await runTests();
```

## Conclusion

The TestRunner library provides a straightforward way to define and run tests for your JavaScript code. With support for asynchronous operations, configurable timeouts, and Chai's `expect` function for assertions, you can ensure that your tests are robust and reliable. Happy testing!

---
Documentation: [https://sebastianwessel.github.io/quickjs/](https://sebastianwessel.github.io/quickjs/)
GitHub Repository: [https://github.com/sebastianwessel/quickjs](https://github.com/sebastianwessel/quickjs)
Submit Feedback: [Create an Issue on GitHub](https://github.com/sebastianwessel/quickjs/issues)